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:
foo
isnil
to start withbar
isnil
- After the
foo=bar
assignment,foo
seems 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.