Learn Go← Dashboard

The error interface, wrapping with %w, errors.Is / errors.As.

Wrapping, Is, and As

Errors often need context as they bubble up — "the file couldn't be opened, because the disk is full, because the quota was exceeded". Go 1.13 introduced error wrapping to handle this without losing the original error.

Wrapping with %w

fmt.Errorf has a special verb %w (only one allowed per call) that wraps another error inside the new one:

data, err := os.ReadFile(path)
if err != nil {
    return fmt.Errorf("load config: %w", err)
}

The returned error has a longer message and still contains the original.

errors.Is — compare error values

When you want to ask "is this error (or any error it wraps) equal to a sentinel?" use errors.Is. It unwraps the chain.

go playground
Loading...

fs.ErrNotExist is a sentinel — a known package-level error value. Define your own with var ErrNoBudget = errors.New("no budget") and callers can errors.Is(err, ErrNoBudget) no matter how deeply you wrap it.

errors.As — pull a typed error out

If your error is a struct (like ValidationError from the last lesson), you want the struct, not just its message. errors.As walks the wrap chain and tries to assign into the target you give it:

go playground
Loading...

The new mantras

  • errors.New(...) / fmt.Errorf(...) — make errors.
  • fmt.Errorf("...: %w", err) — wrap an error with context.
  • errors.Is(err, target) — equal to (or wraps) a sentinel?
  • errors.As(err, &t) — extract a typed error.

You'll use these every day.

You have `err := fmt.Errorf("load: %w", os.ErrNotExist)`. Which call succeeds?