Go's typed nils are gonna get yer
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.
- 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 arenil
. - Concrete vs Interface nil: When you assign a concrete type value (which
happens to be
nil
) to an interface, the interface itself is notnil
. This is because the interface value now contains a type (the concrete type) and a value (nil
).
Explanation
- Declaring
i
as*impl
and initializing it tonil
:Here,var i *impl
i
is a pointer toimpl
and is initialized tonil
. - Printing
i == nil
inmain
:This printsfmt.Println("i == nil:", i == nil)
true
becausei
is anil
pointer toimpl
. - Calling
what(i)
:The functionwhat(i)
what
takes an argument of typeinterface{ do() }
. - Inside
what
function:Whenfunc what(i interface{ do() }) { fmt.Println("i == nil:", i == nil) }
i
(which isnil
) is passed towhat
, it is assigned to the parameteri
of typeinterface{ do() }
. - Interface value construction:
The value ofi
insidewhat
is now an interface that holds:- Type:
*impl
(the concrete type of the value passed in) - Value:
nil
(the value of the concrete type)
- Type:
- Checking
i == nil
:This printsfmt.Println("i == nil:", i == nil)
false
because the interfacei
is notnil
:- The type part of the interface is
*impl
. - The value part of the interface is
nil
.
- The type part of the interface is
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
But before we wrap up... time (once again) for some self-promotion 🙊