Go

When is err == ErrX wrong, and why can comparing errors with == panic?

Question 303HardGo 1.22 to 1.25

== matches only the outermost error. As soon as someone wraps it with %w, the comparison fails. Use errors.Is, which walks the chain. The exceptions are documented unwrapped sentinels such as io.EOF returned by Read, where == io.EOF is idiomatic but errors.Is is still safe.

Comparing two interfaces whose dynamic type is not comparable (slice, map or func-based types) panics at run time. errors.Is guards against this: it only uses == if the target's type is comparable.

type MultiErr []error

func (m MultiErr) Error() string { return "multi" }

func main() {
	var e1 error = MultiErr{io.EOF}
	var e2 error = MultiErr{io.EOF}

	fmt.Println(errors.Is(e1, e2)) // false, no panic
	fmt.Println(e1 == e2)          // panic: runtime error: comparing
	                               // uncomparable type main.MultiErr
}

Takeaways: prefer errors.Is, and give error types that contain slices or maps a pointer receiver, so the dynamic type is a comparable pointer. Also, a switch err { case ErrA: } uses ==, so it has the same wrapping problem. Write switch { case errors.Is(err, ErrA): } instead.

More on Error Handling & panics

All 37 Error Handling & panics questions