Design custom errors in Go. Explain wrapping, errors.Is, errors.As and multi-error trees.
Question 98HardGo 1.22 to 1.25
error is a one-method interface. Go offers three styles, each for a different need:
- Sentinel errors (
var ErrNotFound = errors.New(...)), compared by identity. - Typed errors (structs), which carry extra data.
- Opaque errors, which callers only test for behaviour.
Wrapping with %w keeps a chain that the helper functions can walk.
var ErrNotFound = errors.New("not found")
type QueryError struct {
Query string
Err error
}
func (e *QueryError) Error() string { return e.Query + ": " + e.Err.Error() }
func (e *QueryError) Unwrap() error { return e.Err }
func find() error {
return fmt.Errorf("repo: %w", &QueryError{"SELECT 1", ErrNotFound})
}
err := find()
fmt.Println(errors.Is(err, ErrNotFound)) // true: walks Unwrap chain
var qe *QueryError
if errors.As(err, &qe) { // target is **QueryError
fmt.Println(qe.Query) // SELECT 1
}
// Go 1.20: multiple wrapping forms a tree
joined := errors.Join(ErrNotFound, context.Canceled)
both := fmt.Errorf("a: %w, b: %w", ErrNotFound, io.EOF)
fmt.Println(errors.Is(joined, context.Canceled), errors.Is(both, io.EOF)) // true true
Gotchas:
- The target of
errors.Asmust be a non-nil pointer to a type that implementserror, or to an interface type. Anything else panics. %vformats only the error text, so callers cannot match the cause.%wexposes the wrapped error toerrors.Is/As, which makes it part of your API contract. Choose deliberately.- Custom
Is(target error) boolandAs(any) boolmethods let you customise matching. - Comparing
err == ErrXdirectly fails once the error has been wrapped. Unwrap() []erroris the multi-error form.errors.Isanderrors.Aswalk the tree depth-first.
More on Interfaces, Methods & Embedding
- Q96Why does Go favour small interfaces? Give examples of composition.
- Q97What's wrong with this
String()method? When doesfmtcallString()orError()? - Q99When does comparing two interface values panic? What does this print?
- Q100Does converting a value to an interface allocate? How do interfaces affect escape analysis and performance?
- Q101What's the difference between a method value and a method expression? What does this print?
- Q102On which types can you declare methods? Can you add a method to
int,time.Time, a pointer type, or an interface?