0% completed
The Nil Interface Trap
On This Page
When an Interface Value Is Nil
Methods on a Nil Pointer
The Repairs
Detecting a Typed Nil
Using This in an Interview
Key Takeaways
An order service starts rejecting every order, including orders that are valid. For each valid order, the logs show the same line: save failed: <nil>. The function that checks the order did not find a problem and returned nil, yet the check err != nil in its caller is true.
Output:
save failed: <nil>
The order is valid, validate returned nil, and the program compiles and passes go vet. Why is a result that prints as <nil> not equal to nil, and how should the code be written so that this cannot happen?
When an Interface Value Is Nil
How Interface Values Work showed that an interface value is two words, one for the type and one for the data. An interface value equals nil only when both words are nil. The first word records the dynamic type, and it is nil only while the variable holds no value of any type.
The function validate returns a pointer of type *OrderError, and that pointer is nil. The function save has the result type error, so its return statement converts the pointer to an interface value. The conversion stores the type *OrderError in the first word and the nil pointer in the second word. The result has a type, so it is not a nil interface.
Output:
pointer == nil: true empty == nil: true holdsNilPointer == nil: false
| Variable | Type word | Data word | == nil |
|---|---|---|---|
empty | nil | nil | true |
holdsNilPointer | *OrderError | nil | false |
The expression x == nil, where x is an interface, asks whether the interface holds anything at all. It does not test the stored value. This also applies to interfaces other than error. A nil *bytes.Buffer stored in an io.Writer gives a writer that is not equal to nil, so a check such as if w != nil passes and the first write panics.
The Go documentation covers this in the FAQ, under why a nil error value is not equal to nil.
Methods on a Nil Pointer
A nil pointer can be a valid receiver, and this surprises engineers who expect a method call to fail at once. A method with a pointer receiver is a function whose first parameter is the pointer. Calling it requires the address of the function, and the type alone supplies that address, so the call itself does not read through the pointer. The panic happens only if the body reads or writes through the receiver.
The interface value records its dynamic type, so the call err.Error() finds the method in the itab and runs it with the nil pointer as its receiver. The program below makes that call and shows what happens.
panic: runtime error: invalid memory address or nil pointer dereference
The stack trace places the panic inside Error, at the read of e.OrderID. The function was called and began to run, and only the field read failed. A method that checks its receiver first does not panic at all:
func (e *OrderError) Error() string { if e == nil { return "no order error" } return fmt.Sprintf("order %d failed", e.OrderID) }
A method with a value receiver cannot run on a nil pointer, because the call must first copy the value that the pointer refers to. Through an interface, the generated function from How Interface Values Work reports this with its own text. For a method Label declared on Order, the message is panic: value method main.Order.Label called using nil *Order pointer. If the compiler can prove the dynamic type and call the method directly, the panic is the plain nil pointer message instead. A call through an interface that holds no value at all panics with the nil pointer message, because the first word is nil and there is no method table to read.
The opening program printed <nil> instead of panicking, because fmt handles this case. The function catchPanic in fmt/print.go recovers a panic from an Error or String method, and it prints <nil> if the argument is a nil pointer. That behavior hides the panic, and it makes the log line look like an empty value.
The Repairs
The cause is a function that has a concrete pointer type as its result, and whose result is later converted to an interface. Three repairs exist, and the first two remove the conversion of a nil pointer.
Declare the result as error. This is the usual repair. When validate has the result type error, its return nil produces a nil interface directly, and no pointer is involved.
Output:
<nil> order 1002: no customer
Return a literal nil. Sometimes a function must keep a concrete variable inside its body, for example while it builds up the error. Check that variable, and write nil in the return statement when there is nothing to report.
func save(o Order) error { var failure *OrderError if o.Customer == "" { failure = &OrderError{OrderID: o.ID, Reason: "no customer"} } if failure == nil { return nil // not "return failure", which would set the type word } return failure }
Do not store a typed nil. A nil pointer of a concrete type is called a typed nil, and a parameter of interface type has the same problem with it. A call such as export(buffer), where buffer is a nil *bytes.Buffer and the parameter is an io.Writer, passes a non-nil interface. Any nil check inside export then passes, and the failure appears at the first write. The check belongs in the caller, which knows the concrete type.
| Situation | Repair |
|---|---|
| A function returns an error | Declare the result as error |
| A local variable has a concrete error type | Return a literal nil when it is nil |
| A pointer may be nil before it is passed as an interface | Check it before the conversion |
Do not repair this by comparing with a typed nil inside the caller. The check err == (*OrderError)(nil) does compile and it does return true here. However, it works for one type only, and every caller would have to know which concrete type to test.
Detecting a Typed Nil
When an error prints as <nil> and is still not equal to nil, the verb %T of the fmt package answers the question immediately. It prints the dynamic type of the value. A plain %v prints the same text for both cases, so it cannot tell them apart, while %#v shows both the type and the value.
Output:
empty: %T=<nil> %v=<nil> %#v=<nil> holdsNilPointer: %T=*main.OrderError %v=<nil> %#v=(*main.OrderError)(nil)
A %T result of <nil> means a true nil interface. Any other result means that a typed value is stored. Here, *main.OrderError names the type to search for: some function between the error's source and the failing check returns that pointer type.
Using This in an Interview
An interface value is nil only when both its type word and its data word are nil. A nil pointer stored in an interface sets the type word, so the interface does not equal nil, and this most often appears when a function returns a nil *OrderError as an error. A likely follow-up asks how to prevent it. Declare the result as error and return a literal nil, so no nil pointer is ever converted.
Key Takeaways
- An interface value equals nil only when both its type word and its data word are nil.
- Converting a nil pointer to an interface sets the type word, so the result does not equal nil.
- A method with a pointer receiver can run on a nil pointer, and it panics only when it reads or writes through the receiver.
fmtrecovers that panic and prints<nil>, which hides the cause in logs.- Functions that return errors should declare the result as
errorand return a literalnil. %Ttells the two cases apart, while a plain%vprints<nil>for both.
An interface value that holds a nil pointer is a real value with a known type, even though the pointer inside it is nil. Comparing it with nil asks only whether the interface holds something, and it does. The practical rule is short: when a function reports an error, its result type should be error, and its success path should say return nil.
Reading Progress
0%
On This Page
When an Interface Value Is Nil
Methods on a Nil Pointer
The Repairs
Detecting a Typed Nil
Using This in an Interview
Key Takeaways