Golang Gotcha - Typed Nil
nil is the literal representation of zero in Golang. It can be typed, which is super useful because we can easily check if a value is zero, no matter what type the value is.
It can however lead to some confusing situations.
What do you think this code will print?
var foo interface{}
fmt.Println(foo == nil)
var bar *int
fmt.Println(bar == nil)
foo = bar
fmt.Println(foo == nil)
This is what it prints:
true
true
false
Let’s recap. What seems to be going on here is:
fooisnilto start withbarisnil- After the
foo=barassignment,fooseems to no longer benil🤓
Did this surprise you? If so, read on and this blog post will clear things up for you 👌.
To understand what’s going on here, it’s important to understand two bits: how interfaces work, and how the equal comparision == works.
Interfaces, under the surfaces
Under the hood, interfaces in Go have a dynamic type T and value V. They are set to whatever the concrete type and value the variable ends up being.
When we declare foo with var foo interface{}, foo is a nil interface with type nil and value nil.
When we assign foo = bar, foo has type *int and value nil.
You can see it for yourself by running the following code:
var foo interface{}
fmt.Printf("%T %v \n", foo, foo)
var bar *int
foo=bar
fmt.Printf("%T %v \n", foo, foo)
Which results in:
<nil> <nil>
*int <nil>
Equality, in reality
In our example, when we do foo == nil, we are doing an equality comparision between the interface{} variable foo, and the nil literal.
When we compare a variable to literal, the compiler tries to cast the literal to the declared type of the variable. In this case, we are actually comparing foo to interface{}(nil)
So how are interfaces compared? According to the Golang spec:
Two interface values are equal if they have identical dynamic types and equal dynamic values or if both have value nil.
Let’s go back to our original example:
var foo interface{}
fmt.Println(foo == nil)
var bar *int
fmt.Println(bar == nil)
foo = bar
fmt.Println(foo == nil)
The first foo == nil comparision results in true because the foo has type nil and value nil.
After the assignment foo = bar, foo now has type *int and value nil.
Now, when we compare foo == nil, because of the declared type of foo is interface{}, we are still comparing foo to interface{}(nil). However foo’s dynamic type is now *int. Which means the comparision now results in false.
To reinforce our understanding, let’s explicitly compare foo to (*int)(nil):
var foo interface{}
fmt.Println(foo == nil)
var bar *int
fmt.Println(bar == nil)
foo = bar
fmt.Println(foo == nil)
fmt.Println(foo == (*int)(nil))
true
true
false
true
Why is all this important, tho?
If your function returns an interface type and you depend on the nil check, for example, checking if the returned error is nil or not, be extra careful.
The Golang FAQ has a really great example:
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
If you do if err := returnsError(); err != nil {}, you’ll be wondering why your function returns a non-nil error all the time.