The nil error interface gotcha: what does this print?
Question 302HardGo 1.22 to 1.25
type MyErr struct{}
func (*MyErr) Error() string { return "my err" }
func do(fail bool) error {
var p *MyErr // nil pointer
if fail {
p = &MyErr{}
}
return p // converts *MyErr to error
}
func main() {
err := do(false)
fmt.Println(err == nil) // false
fmt.Printf("%T %v\n", err, err) // *main.MyErr my err
}
An interface value is a (type, value) pair, and it is nil only when both parts are nil. Returning a typed nil pointer produces (*MyErr, nil), a non-nil interface, so the caller treats success as failure. %v still prints "my err" because Error has a pointer receiver and never dereferences it.
Fixes: always return nil explicitly on success, and declare the function's result as error, never *MyErr. Don't store results in a concrete-typed variable before returning them as error.
func do(fail bool) error {
if fail {
return &MyErr{}
}
return nil
}
staticcheck (SA4023) and nilaway can catch some cases. Interviewers ask this to check that you understand how interfaces are represented.
More on Error Handling & panics
- Q300Compare sentinel errors, typed errors and opaque errors. When would you use each?
- Q301How do you design a good custom error type? Does it matter whether
Error()has a pointer or value receiver? - Q303When is
err == ErrXwrong, and why can comparing errors with==panic? - Q304When should you wrap an error and when should you not? How do you handle errors at package boundaries?
- Q305When is it appropriate to
panicinstead of returning an error? - Q306What does this print? (Rules of
recover)