Check for nil and nil interface in Go

后端 未结 4 1288
生来不讨喜
生来不讨喜 2020-12-07 18:48

Currently I\'m using this helper function to check for nil and nil interfaces

func isNil(a interface{}) bool {
  defer func() { recover() }()
  return a == n         


        
相关标签:
4条回答
  • 2020-12-07 18:53

    Two solutions NOT using reflection:

    Copy and paste code into editor at: https://play.golang.org/ to see in action.

    1: Add an "IsInterfaceNil()" function to interface.

    2: Use A "type switch"

    CODE BELOW:

    一一一一一一一一一一一一一一一一一一一一

    EXAMPLE #1: IsInterfaceNil()

    一一一一一一一一一一一一一一一一一一一一

    //:Example #1:
    //:I prefer this method because the 
    //:TakesInterface function does NOT need to know
    //:about all the different implementations of
    //:the interface.
    package main;
    import "fmt";
    
    func main()(){
    
        var OBJ_OK *MyStruct = &( MyStruct{} );
        var NOT_OK *MyStruct = nil;
        
        //:Will succeed:
        TakesInterface( OBJ_OK );
        
        //:Will fail:
        TakesInterface( NOT_OK );
    
    }
    
    func TakesInterface( input_arg MyInterface ){
    
        if( input_arg.IsInterfaceNil() ){
            panic("[InputtedInterfaceIsNil]");
        }
        
        input_arg.DoThing();
    }
    
    type MyInterface interface{
        DoThing()()
        IsInterfaceNil()(bool)
    }
    type MyStruct struct{}
    func(f *MyStruct)DoThing()(){
        fmt.Println("[MyStruct.DoThing]");
    }
    func(f *MyStruct)IsInterfaceNil()(bool){
        if(nil==f){ return true; }
        return false;
    }
    

    一一一一一一一一一一一一一一一一一一一一

    EXAMPLE #2: Type Switch

    一一一一一一一一一一一一一一一一一一一一

    //:Example #2:
    //:This will also work, but the function taking
    //:the interface needs to know about all 
    //:implementations. This defeats a bit of the
    //:decoupling from implementation that an
    //:interface offers, but if you are just using
    //:interfaces for polymorphism, it's probably
    //:an okay way to go. (opinion)
    package main;
    import "fmt";
    
    func main()(){
    
        //:Will succeed:
        var OBJ_OK *IMPLMENTS_INTERFACE_01 = 
                 &( IMPLMENTS_INTERFACE_01{} );
        TakesInterface( OBJ_OK );
        
        //:Will fail:
        var NOT_OK *IMPLMENTS_INTERFACE_01 = nil;
        TakesInterface( NOT_OK );
    }
    
    func TakesInterface( hasDoThing MyInterface ){
    
        //:THIS WILL NOT WORK:
        if(nil==hasDoThing){
            panic("[This_Error_Message_Will_Never_Happen]");
        }
        
        //:TYPE SWITCH TO THE RESCUE:
        switch v := hasDoThing.(type){
        
            case (*IMPLMENTS_INTERFACE_01): 
            if(nil==v){ panic("[Nil_PTR_01]"); }
            
            case (*IMPLMENTS_INTERFACE_02): 
            if(nil==v){ panic("[Nil_PTR_02]"); }
            
            case (*IMPLMENTS_INTERFACE_03): 
            if(nil==v){ panic("[Nil_PTR_03]"); }
            
            default: 
                panic("[UnsupportedInterface]");
        }
        
        hasDoThing.DoThing();
        
    }
    
    type IMPLMENTS_INTERFACE_01 struct{};
    type IMPLMENTS_INTERFACE_02 struct{};
    type IMPLMENTS_INTERFACE_03 struct{};
    func (f *IMPLMENTS_INTERFACE_01)DoThing()(){
        fmt.Println( "DoingTheThing_01" );
    }
    func (f *IMPLMENTS_INTERFACE_02)DoThing()(){
        fmt.Println( "DoingTheThing_02" );
    }
    func (f *IMPLMENTS_INTERFACE_03)DoThing()(){
        fmt.Println( "DoingTheThing_03" );
    }
    
    type MyInterface interface{
        DoThing()()
    }
    

    UPDATE: After implementing in my code base, I found #2 (type switch) to be best solution. Specifically because I DON'T want to EDIT the glfw.Window struct in the bindings library I am using. Here is a paste-bin of my use-case. Apologies for my non-standard coding style. https://pastebin.com/22SUDeGG

    0 讨论(0)
  • 2020-12-07 19:00

    If neither of the earlier options works for you, the best I could came up so far is:

    if c == nil || (reflect.ValueOf(c).Kind() == reflect.Ptr && reflect.ValueOf(c).IsNil())
    

    At least it detects (*T)(nil) cases.

    0 讨论(0)
  • 2020-12-07 19:06

    See for example Kyle's answer in this thread at the golang-nuts mailing list.

    In short: If you never store (*T)(nil) in an interface, then you can reliably use comparison against nil, no need to use reflection. On the other hand, assigning untyped nil to an interface is always OK.

    0 讨论(0)
  • 2020-12-07 19:17

    This is the interface definition for this exmaple solution:

    package checker
    
    import (
        "errors"
    
        "github.com/rs/zerolog"
    )
    
    var (
        // ErrNilChecker returned if Check invoked on a nil checker
        ErrNilChecker = errors.New("attempted Check with nil Checker")
    
        // ErrNilLogger returned if the Check function is provide a nil logger
        ErrNilLogger = errors.New("nil logger provided for Check")
    )
    
    // Checker defines the interface
    type Checker interface {
        Check(logger *zerolog.Logger) error
    }
    

    One of our Checker implementations supports aggregation of Checkers. But testing uncovered the same issue as this thread. This solution uses the reflect package if the simple nil check fails, leveraging the reflect.Value type to resolve the question.

    // AggregateChecker implements the Checker interface, and
    //  supports reporting the results of applying each checker
    type AggregateChecker struct {
        checkers []Checker
    }
    
    func (ac *AggregateChecker) Add(aChecker Checker) error {
        if aChecker == nil {
            return ErrNilChecker
        }
    
        // It is possible the interface is a typed nil value
        // E.g. checker := (&MyChecker)(nil)
        t := reflect.TypeOf(aChecker)
        if reflect.ValueOf(aChecker) == reflect.Zero(t) {
            return ErrNilChecker
        }
    
        ac.checkers = append(ac.checkers, aChecker)
        return nil
    }
    
    0 讨论(0)
提交回复
热议问题