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

Run code in Go Playground

Let’s recap. What seems to be going on here is:

  • foo is nil to start with
  • bar is nil
  • After the foo=bar assignment, foo seems to no longer be nil 🤓

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> 

Run code in Go Playground

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.

Written on May 26, 2019