Go Typed Nil

The following code doesn’t do what you might expect:

package main

import "fmt"

func main() {
	var i *impl

	fmt.Println("i == nil:", i == nil)
	what(i)
}

type impl struct{}

func (i *impl) do() {}

func what(i interface{ do() }) {
	fmt.Println("i == nil:", i == nil)
}

If you expected the what function to print i == nil: true, then keep reading…

Typed Nils

The behavior observed is due to the way interfaces and nil values interact in Go. To understand why the what function is able to see the i argument as non-nil, we need to dig into the details of how Go handles interface values.

  1. Interface Values: In Go, an interface value is a tuple of a type and a value. An interface value is nil only if both the type and the value are nil.
  2. Concrete vs Interface nil: When you assign a concrete type value (which happens to be nil) to an interface, the interface itself is not nil. This is because the interface value now contains a type (the concrete type) and a value (nil).

Explanation

  1. Declaring var i *impl initializes it to nil.
    • i is a pointer to impl and is initialized to nil.
  2. Printing i == nil in main is true
    • because i is a nil pointer to impl
  3. Calling what(i) function:
    • the function what takes an argument of type interface{ do() }
  4. Inside what function:
    • i (which is nil) is passed to what
    • it is assigned to the parameter i of type interface{ do() }
  5. Interface value construction:
    The value of i inside what is now an interface that holds:
    • Type: *impl (the concrete type of the value passed in)
    • Value: nil (the value of the concrete type)
  6. Checking i == nil prints false because the interface i is not nil:
    • The type part of the interface is *impl.
    • The value part of the interface is nil.

Summary

The what function sees the i argument as non-nil because, in Go, an interface holding a nil pointer is not itself nil. The interface contains type information (*impl) and a nil value. Therefore, when the code checks if i is nil, it returns false since the type information (*impl) is present.

Reference material