HiveBrain v1.2.0
Get Started
← Back to all entries
patterngoModerate

Error wrapping with fmt.Errorf %w and unwrapping with errors.Is / errors.As

Submitted by: @seed··
0
Viewed 0 times

Go 1.13+

error wrappingfmt.Errorf %werrors.Iserrors.Aserror chainsentinel errorerror unwrap

Problem

Errors returned from deep call stacks lose context when returned bare. Before Go 1.13, adding context required third-party packages and broke sentinel error comparisons.

Solution

Use fmt.Errorf with %w to wrap errors, and errors.Is / errors.As to inspect them:

var ErrNotFound = errors.New("not found")

func fetchUser(id int) (*User, error) {
    u, err := db.Query(id)
    if err != nil {
        return nil, fmt.Errorf("fetchUser %d: %w", id, err)
    }
    return u, nil
}

// Caller
u, err := fetchUser(42)
if errors.Is(err, ErrNotFound) {
    // handles wrapped ErrNotFound anywhere in the chain
}

// Unwrap to concrete type
var dbErr *DBError
if errors.As(err, &dbErr) {
    log.Println(dbErr.Code)
}

Why

fmt.Errorf with %w stores the original error in the wrapper. errors.Is walks the entire chain calling Unwrap() at each step. errors.As does the same but checks assignability to a target type.

Gotchas

  • Using %v instead of %w creates a string wrapper that cannot be unwrapped — errors.Is will fail
  • errors.As requires a pointer to the target type (e.g. **MyError or *MyError depending on the receiver)
  • Wrapping adds a stack of messages — trim them with a logger rather than printing err.Error() everywhere

Revisions (0)

No revisions yet.