Go by Example

Errors

errors.New, fmt.Errorf with %w, errors.Is, errors.As, and sentinel errors - Go errors are values.

In Go, errors are values returned from functions - not thrown exceptions. A function that can fail returns (result, error) and callers decide what to do at each call site.

errors.New creates a simple error with a static message. fmt.Errorf creates an error with a formatted message and, when you use %w, wraps another error so callers can inspect the chain.

package main
 
import (
    "errors"
    "fmt"
)
 
var ErrNotFound = errors.New("not found")
 
func findUser(id int) (string, error) {
    if id != 1 {
        return "", fmt.Errorf("findUser %d: %w", id, ErrNotFound)
    }
    return "alice", nil
}
 
func main() {
    name, err := findUser(2)
    if err != nil {
        fmt.Println("error:", err)
 
        if errors.Is(err, ErrNotFound) {
            fmt.Println("user does not exist")
        }
        return
    }
    fmt.Println("found:", name)
}

errors.Is walks the entire error chain looking for a match, so it works even when the sentinel is wrapped several levels deep:

// wrapping three levels deep
err := fmt.Errorf("layer3: %w", fmt.Errorf("layer2: %w", ErrNotFound))
fmt.Println(errors.Is(err, ErrNotFound)) // true

errors.As extracts a typed error from the chain - useful when the error carries structured fields:

type ValidationError struct {
    Field string
    Msg   string
}
 
func (e *ValidationError) Error() string {
    return fmt.Sprintf("validation: %s %s", e.Field, e.Msg)
}
 
func validate(age int) error {
    if age < 0 {
        return &ValidationError{Field: "age", Msg: "must be non-negative"}
    }
    return nil
}
 
err := validate(-1)
var ve *ValidationError
if errors.As(err, &ve) {
    fmt.Println("field:", ve.Field) // field: age
}

In production

Wrap errors with %w to create an inspectable chain - callers use errors.Is / errors.As without string matching. Use sentinel errors (var ErrNotFound = errors.New(...)) for expected conditions callers must handle explicitly; use wrapped errors for unexpected conditions that need context. Never swallow errors silently - at minimum log them. if err != nil { return err } is idiomatic Go error propagation; it is a deliberate design choice to make every failure path explicit.

Enjoyed this? Get more essays on software craft delivered to your inbox.

Subscribe free