Golang Footgun: Why is this interface not nil?
I'm gonna show you a snippet of a Go code and you tell me what the output is gonna be.
package main
import "fmt"
type MyCustomType struct {}
func getResult() any {
var t *MyCustomType
return t
}
func main() {
result := getResult()
fmt.Println(result == nil)
}
// Try it out: https://goplay.tools/snippet/-axDsS493LZHere's what went through my mind:
- variable
tis of type*MyCustomType tis nilgetResultreturns the nilt- so, result must also be nil
Let's see what happens when we run the code.
go run main.go
falseThe crucial bit of information here is that the function returns an interface - in this case
anyor an empty interfaceinterface{}.
Explanation
In Go, an interface is a pair of a type & a value (T, V).
Eg: in var myInterface any = 3, the interface myInterface has a type int and a value 3.
For an interface to be nil, both its type and value must be nil. The following example shows a nil interface:
var a any
fmt.Println(a == nil) // true
// Try it: https://go.dev/play/p/X0h-DCswGPGIt's nil because it hasn't been assigned any concrete value yet.
On the contrary, take a look at the following code:
var a *int
var b any = a
fmt.Printf("typeof a: %T\n", a)
fmt.Printf("typeof b: %T\n", b)
fmt.Printf("a == nil: %v\n", a == nil)
fmt.Printf("b == nil: %v\n", b == nil)
// Try it: https://go.dev/play/p/3gq25jCzepxThe interface b is not nil as its type is *int and its value is nil.
It's important to distinguish that the interface holds in a nil pointer, but it's not nil itself.
The above program outputs:
typeof a: *int
typeof b: *int
a == nil: true
b == nil: falseThis is also addressed in the Go FAQ.
How to Check the Underlying Value for Nil
So, what's the correct way to check if the underlying value is nil when you receive an interface? The two approaches are:
Type assertion
package main
import "fmt"
type MyCustomType struct{}
func getResult() any {
var t *MyCustomType
return t
}
func main() {
result := getResult()
if v, ok := result.(*MyCustomType); ok {
fmt.Printf("v is nil: %v, result is nil: %v\n", v == nil, result == nil)
}
}
// Try it out: https://goplay.tools/snippet/MeMUtzKG9LTReflection
Type assertion works fine when you know exactly what type the function returns.
In our case, we know that the function returns a pointer to MyCustomType.
But, imagine cases where getResult() could conditionally return various types. We would need to assert all the possible types that getResult() could return. Or, it could also return an unexported type so we cannot assert it.
In such cases, we can use reflection to check if the result is nil.
package main
import (
"fmt"
"reflect"
)
type MyCustomType struct{}
func getResult() any {
var t *MyCustomType
return t
}
func main() {
result := getResult()
fmt.Printf("result is nil: %v\n", result == nil) // false
fmt.Printf("result contains nil: %v\n", reflect.ValueOf(result).IsNil()) // true
}
// Try it out: https://goplay.tools/snippet/ZxJNYBW0pVzI must say that using reflection is generally frowned upon as it adds a lot of runtime overhead. There are also some caveats.
Eg: reflect.ValueOf(result).IsNil() can panic for certain types like a string or an int.
Here's a generic helper function that can be used to check if the underlying value is nil.
func isNil(i any) bool {
if i == nil {
return true
}
v := reflect.ValueOf(i)
k := v.Kind()
// Check for kinds that have a concept of nilness
switch k {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
// For these kinds, IsNil() reports whether the value is nil
return v.IsNil()
default:
// Other kinds (structs, basic types, arrays) cannot be nil themselves
return false
}
}