Craig Weber

Go's interfaces and nil by example

I've recently been involved in conversations with a few Go developers who have expressed frustration about Go's interfaces with respect to nil. It seems not everyone understands that interfaces are a reference type (like a pointer), and they can reference other reference types (i.e., pointers, maps, slices, etc). Because all reference types have a nil (zero) value, an interface can be nil or an interface can reference a nil pointer; people may fail to realize that the nility of the interface is independent from the nility of the thing it points to, and when we ask (for example) if err != nil, we're actually asking is the interface nil?, not is the value behind the interface nil?. Here are a few examples that will (hopefully) demonstrate this clearly:

var nilInterface interface{} = nil
println("nil interface is nil?:", nilInterface == nil) // true
println(nilInterface) // (0x0, 0x0)

Note that printing the nil interface gives (0x0, 0x0). An interface has 2 components: type information and value information. In the case of a nil interface, both components are set to zero.

var value int = 10
var interfaceToValue interface{} = value
println("interface-to-value is nil?:", interfaceToValue == nil) // false
println("value address:", &value)
println(interfaceToValue)

Here, we see the address of the value is something like 0xc420041f08, and printing the interface gives: (0x52bc0,0xc420041f08). The value component is the address of the value from which we created the interface (this is what we mean when we say interfaces are reference types). The type component is also non-zero.

var p *int = nil
var interfaceToNilPtr interface{} = p
println("interface-to-nil-ptr is nil?:", interfaceToNilPtr == nil) // false
println("nil ptr:", p)
println(interfaceToNilPtr)

When we test an interface to a nil *int for nility, we see that it is not nil! This is probably the more surprising case for some developers. When we print the nil pointer, we see that it's address is 0x0, the nil address. We also see that printing the interface gives (0x52bc0,0x0). Here we have type information, but our value pointer is nil.

var i int = 10
var p2 *int = &i
var ifaceToNonNilPtr interface{} = p2
println("iface-to-non-nil-ptr is nil?:", ifaceToNonNilPtr == nil) // false
println("non-nil-pointer:", p2)
println(interfaceToNonNilPtr)

Finally, we have an interface to a non-nil *int. When we print the non-nil pointer, we see its address is something like 0xc420041f10, and printing the interface gives (0x52bc0,0xc420041f10). Notice that the type information (0x52bc0) is the same as the previous nil-*int example (because a nil *int has the same type but different value than a non-nil *int). Also notice that again, the pointer's value matches the value in the interface.

The complete, runnable example can be found here.