Error Handling Created: 2024-01-27 Updated: 2024-01-27

Error Wrapping

A pattern for adding context to errors while preserving the original error for inspection.

Contributors

When to Use

  • When you need to add context to errors as they propagate up the call stack
  • When you want to preserve the original error for programmatic inspection
  • When debugging requires understanding the full error chain

When NOT to Use

  • For simple programs where error context isn't needed
  • When exposing internal error details would be a security concern

Error wrapping in Go allows you to add context to errors while preserving the original error. Since Go 1.13, the standard library provides fmt.Errorf with the %w verb and the errors.Is/errors.As functions for working with wrapped errors.

Implementation

package repository

import (
    "errors"
    "fmt"
)

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

type User struct {
    ID   int
    Name string
}

type UserRepository struct {
    db map[int]User
}

func (r *UserRepository) GetByID(id int) (User, error) {
    user, ok := r.db[id]
    if !ok {
        // Wrap sentinel error with context
        return User{}, fmt.Errorf("user %d: %w", id, ErrNotFound)
    }
    return user, nil
}

func (r *UserRepository) UpdateName(id int, name string) error {
    user, err := r.GetByID(id)
    if err != nil {
        // Wrap error with additional context
        return fmt.Errorf("updating name: %w", err)
    }

    user.Name = name
    r.db[id] = user
    return nil
}

Usage

package main

import (
    "errors"
    "fmt"
)

func main() {
    repo := &UserRepository{db: make(map[int]User)}

    err := repo.UpdateName(42, "Alice")
    if err != nil {
        // Check for specific error type
        if errors.Is(err, ErrNotFound) {
            fmt.Println("User does not exist")
        }

        // Full error message includes all context
        // Output: "updating name: user 42: not found"
        fmt.Println(err)
    }
}

// Unwrapping to access the original error
func handleError(err error) {
    var notFound *NotFoundError
    if errors.As(err, &notFound) {
        fmt.Printf("Resource %s not found\n", notFound.Resource)
    }
}

Benefits

  • Preserves error chain: Original errors remain accessible via errors.Is and errors.As
  • Adds debugging context: Each layer can add relevant information
  • Standard library support: No external dependencies required
  • Backwards compatible: Works with existing error handling code
  • Enables precise error handling: Callers can check for specific error types at any level