Why is an interface holding a nil pointer not equal to nil? What does this print?
type MyErr struct{}
func (*MyErr) Error() string { return "boom" }
func do(fail bool) error {
var p *MyErr // nil pointer
if fail {
p = &MyErr{}
}
return p // BUG: always returns a non-nil interface
}
func main() {
err := do(false)
fmt.Println(err == nil) // false
fmt.Printf("%T %v\n", err, err) // *main.MyErr boom
}
An interface value is a pair: (dynamic type, dynamic value). It equals nil only if both parts are nil. Returning a typed nil pointer produces (*MyErr, nil). Its type is set, so the interface is not nil, and err != nil is true. Calling err.Error() still works here, because the method doesn't dereference its receiver.
The fix is to return the literal nil for the interface. Don't let a concrete pointer type reach an interface return value:
func do(fail bool) error {
if fail {
return &MyErr{}
}
return nil
}
To check whether the value inside is nil, use reflect.ValueOf(err).IsNil(), but only for kinds that can be nil. What the interviewer wants to hear: an explanation of the two-word interface layout (itab/_type plus a data pointer), and the rule that functions should return the error interface, never a concrete error pointer type. Note that reflect.ValueOf(err).IsNil() itself panics when err is a true nil interface, so check err != nil first. No default go vet check catches the typed-nil return. Staticcheck's SA4023 flags some comparisons of this kind that can never be true, but code review is the main defence.
More on Language Fundamentals & Types
- Q29What is addressability, and why can't you call a pointer method on a map element?
- Q30How does struct embedding affect method sets and name resolution?
- Q32What is the difference between a defined type (
type A B) and an alias (type A = B)? - Q33Arrays vs slices as values: what does this print?
- Q34How does Go handle integer overflow, division, and conversions between numeric types?
- Q35How did loop variable semantics change in Go 1.22, and what does this print?