How do %w wrapping and errors.Is / errors.As work together in Go?
Given a sentinel error ErrNotFound and a concrete error type QueryError (which implements Unwrap), implement find so a caller can both match the sentinel with errors.Is and extract the concrete *QueryError with errors.As.
Requirements:
- add context to the returned error while preserving a link to the underlying cause
- the returned error must let
errors.Is(err, ErrNotFound)succeed - the returned error must let
errors.As(err, &qe)recover the*QueryError
func find(id string) error {
// your code here
}
Write the implementation.
fmt.Errorf("...: %w", err) wraps err, adding context while keeping a link to it via an Unwrap chain. errors.Is walks that chain to match a sentinel value; errors.As walks it to find an error of a concrete type and assign it into a target pointer.
- ✗Using
%vinstead of%w, which loses theUnwraplink soerrors.Is/errors.Ascan no longer match through it - ✗Comparing wrapped errors with
==instead oferrors.Is, which fails once context is added - ✗Passing a non-pointer to
errors.As, which panics — the target must be a pointer to an error-implementing type
- →Why does wrapping with
%wrequire the error to implement anUnwrapmethod underneath? - →When would you wrap with multiple
%wverbs in onefmt.Errorfcall?
Wrapping and unwrapping
package main
import (
"errors"
"fmt"
)
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(id string) error {
// add context while keeping the link to ErrNotFound via %w
return fmt.Errorf("find %s: %w", id, &QueryError{Query: id, Err: ErrNotFound})
}
func main() {
err := find("u-42")
// errors.Is walks the Unwrap chain down to the sentinel value
fmt.Println(errors.Is(err, ErrNotFound)) // true
// errors.As finds an error of a concrete type and stores it into qe
var qe *QueryError
if errors.As(err, &qe) {
fmt.Println("query was:", qe.Query) // query was: u-42
}
}
%w builds the Unwrap chain. errors.Is compares each link against the target (via == or an Is method), and errors.As looks for a link of the requested type and assigns it into the &qe pointer. Swapping %w for %v would break the chain, and both walks would return false.