Why does this function return a non-nil error even when nothing failed? How do you fix it?
Question 80HardGo 1.22 to 1.25
type MyErr struct{ Msg string }
func (e *MyErr) Error() string { return e.Msg }
func validate(ok bool) error {
var e *MyErr
if !ok {
e = &MyErr{"invalid"}
}
return e // BUG
}
func main() {
if err := validate(true); err != nil {
fmt.Println("failed:", err) // runs and prints "failed: <nil>"
}
}
return e converts a nil *MyErr to error. The result has type word *MyErr and data word nil, so it is not equal to nil. The caller sees a failure. fmt recovers the panic from a nil-receiver Error() and prints <nil>, but any direct call such as err.Error() dereferences the nil pointer (e.Msg) and panics.
Fix: return the error interface directly and write a literal nil on the success path. Do not declare error results with concrete pointer types.
func validate(ok bool) error {
if !ok {
return &MyErr{"invalid"}
}
return nil
}
The same trap appears when a function returns *MyErr and the caller assigns the result to an error variable. The idiom is that exported functions return error, not concrete error types. staticcheck's SA4023 flags this pattern: a comparison of an interface with nil whose result can never change.
More on Interfaces, Methods & Embedding
- Q78What is an itab, when is it built, and what does a method call through an interface cost?
- Q79What does this print? (nil interface vs interface holding a nil pointer)
- Q81Explain method sets. Why does
*Tsatisfy an interface whenTdoes not? - Q82Why doesn't
m["k"].Inc()compile whenInchas a pointer receiver? What other values are not addressable? - Q83How do you decide between value and pointer receivers?
- Q84Compare
v := x.(T)withv, ok := x.(T). What happens whenTis an interface type?