interfaceguard finds subtle interface-related issues, including improper comparisons and type mismatches.
Example:
package main import ( "fmt" "time" ) type Greeter interface { SayHi() string } type CostcoGreeter struct {} func (c *CostcoGreeter) SayHi() string { return "Welcome to Costco, I love you." } func main() { var greeter Greeter = getCachedGreeter() if greeter == nil { panic("greeter is nil") // we might expect this to panic, but it doesn't! } fmt.Println("ok") } // getCachedGreeter simulates cache miss at runtime and will always return nil *CostcoGreeter func getCachedGreeter() Greeter { now := time.Now().Unix() switch { case now == 0: return new(CostcoGreeter) case now == 1: return nil default: var c *CostcoGreeter return c } }
This is a safer way to check if an interface is nil:
package main import ( "fmt" "reflect" "time" ) type Greeter interface { SayHi() string } type CostcoGreeter struct {} func (c *CostcoGreeter) SayHi() string { return "Welcome to Costco, I love you." } func main() { var greeter Greeter = getCachedGreeter() if isNil(greeter) { panic("greeter is nil") // now we catch the nil greeter and panic as expected } fmt.Println("ok") } // getCachedGreeter simulates cache miss at runtime and will always return nil *CostcoGreeter func getCachedGreeter() Greeter { now := time.Now().Unix() switch { case now == 0: return new(CostcoGreeter) case now == 1: return nil default: var c *CostcoGreeter return c } } func isNil(val any) bool { //nolint:interfaceguard if val == nil { return true } v := reflect.ValueOf(val) //nolint:exhaustive switch v.Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice: return v.IsNil() default: return false } }
What about comparing two interface values to each other:
package main import ( "fmt" "time" ) type Greeter interface { SayHi() string } type WallyWorldGreeter struct {} func (w *WallyWorldGreeter) SayHi() string { return "Sorry, folks! We're closed for two weeks to clean and repair America's favorite family fun park. Sorry, uh-huh, uh-huh, uh-huh!" } func main() { var greeterA Greeter var greeterB Greeter = getCachedGreeter() if greeterA == greeterB { panic("greeterA == greeterB") // we might expect this to panic since both are nil, but it doesn't! } fmt.Println("ok") } // getCachedGreeter simulates cache miss at runtime and will always return nil *WallyWorldGreeter func getCachedGreeter() Greeter { now := time.Now().Unix() switch { case now == 0: return new(WallyWorldGreeter) case now == 1: return nil default: var w *WallyWorldGreeter return w } }
Interface equality checks can be tricky! Here is a safer approach:
package main import ( "fmt" "reflect" "time" ) type Greeter interface { SayHi() string } type WallyWorldGreeter struct {} func (w *WallyWorldGreeter) SayHi() string { return "Sorry, folks! We're closed for two weeks to clean and repair America's favorite family fun park. Sorry, uh-huh, uh-huh, uh-huh!" } func main() { var greeterA Greeter var greeterB Greeter = getCachedGreeter() if isEqual(greeterA, greeterB) { panic("greeterA == greeterB") // now we see them as equal and panic as expected } fmt.Println("ok") } // getCachedGreeter simulates cache miss at runtime and will always return nil *WallyWorldGreeter func getCachedGreeter() Greeter { now := time.Now().Unix() switch { case now == 0: return new(WallyWorldGreeter) case now == 1: return nil default: var w *WallyWorldGreeter return w } } func isEqual(greeterA, greeterB Greeter) bool { if isNil(greeterA) && isNil(greeterB) { return true } return reflect.DeepEqual(greeterA, greeterB) } func isNil(val any) bool { //nolint:interfaceguard if val == nil { return true } v := reflect.ValueOf(val) //nolint:exhaustive switch v.Kind() { case reflect.Chan, reflect.Func, reflect.Map, reflect.Pointer, reflect.UnsafePointer, reflect.Interface, reflect.Slice: return v.IsNil() default: return false } }
How To Use
Use it directly via command-line:
go install github.com/jkeys089/interfaceguard/cmd/interfaceguard@latest # default all checks enabled interfaceguard ./... # disable interface-to-interface comparison checks interfaceguard -i ./... # disable interface nil comparison checks interfaceguard -n ./...
Or use the golangci-lint plugin (see: plugin/golangci)