What does this print? (Which side's Is method gets called)
Question 327HardGo 1.22 to 1.25
var ErrNotFound = errors.New("not found")
type NotFoundError struct{ Name string }
func (e *NotFoundError) Error() string { return e.Name + ": not found" }
func (e *NotFoundError) Is(target error) bool { return target == ErrNotFound }
func main() {
err := fmt.Errorf("load: %w", &NotFoundError{"cfg"})
fmt.Println(errors.Is(err, ErrNotFound)) // true
fmt.Println(errors.Is(ErrNotFound, &NotFoundError{"cfg"})) // false
fmt.Println(errors.Is(err, &NotFoundError{"cfg"})) // false
}
errors.Is is not symmetric. It only calls Is on the errors in the first argument's chain, never on the target. Line 1 is true because the *NotFoundError in the chain says it matches ErrNotFound. Line 2 is false because ErrNotFound (an *errorString) has no Is method and is not == to the target. Line 3 is false because the two *NotFoundError values are different pointers, and the custom Is only matches ErrNotFound.
This lets a typed error also "be" a sentinel, so callers can use a cheap errors.Is(err, ErrNotFound) check and still reach the details with errors.As. Keep the argument order straight: always errors.Is(err, target).
More on Error Handling & panics
- Q325What is the
io.Readererror contract, and why isif err != nil { return }wrong there? - Q326Implement a wrapper error type that adds context and works with
errors.Is/As. What happens if you forgetUnwrap? - Q328What happens when a deferred function panics while the goroutine is already panicking?
- Q329What happens if the function passed to
sync.Once.Dopanics? How dosync.OnceFuncandsync.OnceValuediffer? - Q330What are the performance costs of errors,
deferandpanic/recoverin hot paths?