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/-axDsS493LZ
Here's what went through my mind:
- variable
t
is of type*MyCustomType
t
is nilgetResult
returns the nilt
- so, result must also be nil
Let's see what happens when we run the code.
$ go run main.go
false
The crucial bit of information here is that the function returns an interface - in this case
any
or 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-DCswGPG
It'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/3gq25jCzepx
The 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: false
This 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/MeMUtzKG9LT
Reflection
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/ZxJNYBW0pVz
I 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
}
}