The Nil Interface Trap

An interface holding a nil pointer is not itself nil, which is why a nil check can pass and the value can still be nothing.

An interface value is a pair. One half is the concrete type stored in it. The other half is the value of that type. The interface equals nil only when both halves are empty, which means nothing was ever stored in it.

Store a nil pointer and only the second half is empty. The type half says *Robot. The pair is not empty, so the interface is not nil, even though the pointer inside it is. A pointer is an address, and a nil pointer is an address of nothing.

How the bug appears

The usual shape is a function that declares a variable of pointer type, leaves it nil on the failure path, and returns it as an interface. Every caller writes the obvious check and every check passes.

func broken() Cache {
	var c *memCache // nil pointer
	return c        // interface holding type *memCache
}

// This prints false.
fmt.Println(broken() == nil)

It hides well, because methods declared on a pointer receiver often work fine on a nil receiver. The program keeps running until some method reads a field, and then it crashes far from the cause.

The fixes

Return the untyped nil literal on the failure path, not a typed nil variable. Better still, return the concrete pointer type from the function and let the caller decide when to wrap it in an interface: a *memCache compared with nil behaves exactly as it reads.

Example

Example · go
package main

import "fmt"

type Speaker interface {
	Speak() string
}

type Robot struct{}

// Speak works even on a nil *Robot, because it reads no fields.
func (r *Robot) Speak() string { return "beep" }

// build returns a nil *Robot when name is empty. The interface it
// returns is still not nil: the type half holds *Robot.
func build(name string) Speaker {
	var r *Robot
	if name != "" {
		r = &Robot{}
	}
	return r
}

func main() {
	s := build("")

	// The value half is empty, the type half is not.
	fmt.Println(s == nil)
	fmt.Printf("%T\n", s)

	// The method still runs, which is why the bug stays hidden.
	fmt.Println(s.Speak())
}

// Output:
// false
// *main.Robot
// beep

When to use it

  • Explain why a function's result passes != nil and still behaves as if nothing was returned.
  • Fix a constructor whose failure path returns a typed nil variable instead of nil.
  • Identify the hidden type inside a stubbornly non-nil interface value with %T.

More examples

Looking at both halves of the pair

Printing with %v and %T side by side shows why the check surprises people: the printed value is <nil> in both cases, but only one of them has an empty type half. Use this when debugging a nil check that refuses to fire, because %T immediately names the type that is making the interface non-nil.

Example · go
package main

import "fmt"

type Speaker interface {
	Speak() string
}

type Robot struct{}

func (r *Robot) Speak() string { return "beep" }

func main() {
	// Nothing stored: both halves empty.
	var empty Speaker
	fmt.Printf("%v %T %v\n", empty, empty, empty == nil)

	// A nil pointer of a real type.
	var r *Robot
	var s Speaker = r

	// The type half is set, so the interface is not nil.
	fmt.Printf("%v %T %v\n", s, s, s == nil)

	// Compared against the same typed nil, it does match: both
	// halves are equal.
	fmt.Println(s == (*Robot)(nil))
}

// Output:
// <nil> <nil> true
// <nil> *main.Robot false
// true

The bug and the one-line fix

The two functions differ only in how the failure path returns, and that single line decides whether every caller's nil check works. Apply this whenever a constructor or lookup returns an interface and can legitimately return nothing.

Example · go
package main

import "fmt"

type Cache interface {
	Len() int
}

type memCache struct {
	items map[string]string
}

func (m *memCache) Len() int { return len(m.items) }

// broken: c has type *memCache, so the returned interface always
// carries that type, even when c is nil.
func broken(enabled bool) Cache {
	var c *memCache
	if enabled {
		c = &memCache{items: map[string]string{"a": "1"}}
	}
	return c
}

// fixed: return the untyped nil literal on the failure path.
func fixed(enabled bool) Cache {
	if !enabled {
		return nil
	}
	return &memCache{items: map[string]string{"a": "1"}}
}

func main() {
	fmt.Println(broken(false) == nil)
	fmt.Println(fixed(false) == nil)
	fmt.Println(fixed(true).Len())
}

// Output:
// false
// true
// 1

Keep the concrete type in the signature

Returning the struct pointer rather than the interface removes the trap instead of working around it, and it keeps every method of the concrete type available to callers. This is the reason behind the house rule of accepting interfaces and returning structs.

Example · go
package main

import "fmt"

type Cache interface {
	Len() int
}

type memCache struct {
	items map[string]string
}

func (m *memCache) Len() int { return len(m.items) }

// newCache returns the concrete pointer type, so nil stays honest:
// no interface is involved in the comparison.
func newCache(enabled bool) *memCache {
	if !enabled {
		return nil
	}
	return &memCache{items: map[string]string{}}
}

func main() {
	c := newCache(false)
	fmt.Println(c == nil)

	// Wrap it only once it is known to be non-nil.
	var cache Cache
	if c != nil {
		cache = c
	}
	fmt.Println(cache == nil)
}

// Output:
// true
// true

Discussion

  • Be the first to comment on this lesson.