# Errors

> Source: https://learn-go.org/errors/
> Part of Learn Go, free to read.

An error in Go is just a value implementing one method:

```go
type error interface {
    Error() string
}
```

That is the entire mechanism. No exceptions, no stack unwinding, no invisible control flow.

## The basic shape

```go
func loadUser(id string) (*User, error) {
    row, err := db.Query(id)
    if err != nil {
        return nil, fmt.Errorf("load user %s: %w", id, err)
    }
    return row, nil
}
```

Add context at every level and you get a message that describes the whole path:

```text
handle request: load user 42: query users: connection refused
```

## `%w` versus `%v`

```go
fmt.Errorf("load user: %w", err)   // wraps — errors.Is / errors.As can see through
fmt.Errorf("load user: %v", err)   // formats — the chain is broken
```

The printed message is identical. The difference only appears when a caller tries to inspect the error, which is why this bug survives review. `errorlint` in `golangci-lint` catches it.

## Sentinel errors

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

func find(id string) (*User, error) {
    if !exists(id) {
        return nil, fmt.Errorf("find %s: %w", id, ErrNotFound)
    }
    ...
}

if errors.Is(err, ErrNotFound) {   // works through any depth of wrapping
    return http.StatusNotFound
}
```

Use `errors.Is`, never `==`. A direct comparison fails as soon as anything in the chain wraps the error, and the failure is silent.

## Custom error types

When callers need data, not just identity:

```go
type ValidationError struct {
    Field  string
    Reason string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("field %s: %s", e.Field, e.Reason)
}

var ve *ValidationError
if errors.As(err, &ve) {
    log.Printf("bad field: %s", ve.Field)
}
```

`errors.As` finds the first error in the chain of that type and assigns it.

## Joining errors

```go
err := errors.Join(err1, err2, err3)   // nil entries are ignored
```

Useful for validating many fields and reporting all the failures at once, rather than making the user fix them one at a time. `errors.Is` works across all of them.

## Handle once

The most common style mistake is logging an error and also returning it, at every level. The result is one failure reported five times.

```go
// wrong — logged here and again by every caller
if err != nil {
    log.Printf("failed: %v", err)
    return err
}

// right — add context, return, let the top of the stack decide what to do
if err != nil {
    return fmt.Errorf("load config: %w", err)
}
```

Log where you handle. Wrap everywhere else.

## Ignoring an error

Explicit, and greppable:

```go
_ = f.Close()      // deliberate: read-only file, close error is not actionable
```

The blank identifier is the only way to discard an error, which means every discarded error is visible in a diff. `errcheck` will flag the ones you did not mark.

## When to panic

Almost never in library code. Panic is for programmer errors that make continuing meaningless — an impossible state, a failed invariant at startup.

```go
var tmpl = template.Must(template.ParseFiles("index.html"))  // fine: startup, unrecoverable
```

Recovering from a panic is for the top-level boundary of a server, so one bad request does not take down the process — and even then, log it and treat it as a bug.

## Common questions

### Is `if err != nil` not exhausting?

It is verbose, and it makes every failure path visible at the point it can happen. That trade looks better the more code you are reviewing rather than writing — you can see what happens when something fails without leaving the function.

### Should every error be wrapped?

Wrap when you can add context the caller does not have — which id, which file, which operation. Do not wrap just to prepend the function name; that adds noise without information.

### Sentinel error or custom type?

Sentinel when callers only need to know *which* error it is. Custom type when they need data from it. Both work through `errors.Is` and `errors.As` at any wrapping depth.
