# Errors as values and wrapping

> Return errors, add context with %w, and inspect the chain with errors.Is and errors.As.

Concept · Go · https://levelcode.org/concepts/error-wrapping

Go has no exceptions for ordinary failures. A function that can fail returns an `error` as its last value, and the caller checks it right away:

```go
cfg, err := loadConfig(path)
if err != nil {
	return fmt.Errorf("load config %q: %w", path, err)
}
```

`error` is just an interface with one method, `Error() string`. That makes errors ordinary values you can return, store, compare and wrap.

## Wrapping adds context

Each layer adds what it was doing when the error happened, and `%w` keeps the original error inside:

```text
create order: reserve stock for sku "A-17": query items: context deadline exceeded
```

That one line reads like a stack trace written for humans. Make each layer's message short: say what was being attempted, without starting with "failed to" or "error".

## Inspecting the chain

Never compare error strings. Use the two functions that walk the chain of wrapped errors:

```go
var ErrNotFound = errors.New("not found")        // sentinel value

if errors.Is(err, ErrNotFound) { /* 404 */ }     // is any error in the chain this value?
if errors.Is(err, context.DeadlineExceeded) { /* timed out */ }

// custom type with fields: is any error in the chain a *pgconn.PgError? (Go 1.26+)
if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok && pgErr.Code == "23505" { /* unique violation → 409 */ }
```

- **Sentinel values** (`io.EOF`, `sql.ErrNoRows`) are for conditions callers branch on.
- **Error types** carry data (a code, a field name), and you check them with `errors.AsType`. Before Go 1.26, declare a variable and pass its address to `errors.As(err, &pgErr)`; you'll still see that form everywhere.
- `errors.Join(a, b)` combines several errors, and `Is`/`As` look inside every one of them.

## Habits

- **Handle an error once.** Either log it or return it, not both, or you'll log the same failure at every layer.
- **Use `%w` deliberately.** Wrapping makes the inner error part of your API, because callers can now `errors.Is` against it. Use `%v` when you don't want callers depending on an implementation detail, such as the database driver's error type.
- **Translate at the boundary.** Map domain errors to status codes in one place, usually [middleware](https://levelcode.org/concepts/middleware) or a helper that writes responses, rather than in every handler.
- **`panic` is for programmer bugs and impossible states,** not for "file not found."

[Context cancellation](https://levelcode.org/concepts/context-cancellation) errors are ordinary values too: check `errors.Is(err, context.Canceled)` to tell "the client went away" from a real failure.
