Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/golang/go/llms.txt

Use this file to discover all available pages before exploring further.

The errors package implements functions to manipulate errors. It provides functionality for creating, wrapping, and inspecting errors.

Creating Errors

Basic Error Creation

import "errors"

// Create a simple error
var ErrNotFound = errors.New("resource not found")

func findUser(id int) error {
    if id < 0 {
        return errors.New("invalid user ID")
    }
    return nil
}

Formatted Errors with fmt.Errorf

import "fmt"

func validateAge(age int) error {
    if age < 0 {
        return fmt.Errorf("invalid age: %d", age)
    }
    return nil
}

Error Wrapping

Wrapping with %w

func readConfig(filename string) error {
    data, err := os.ReadFile(filename)
    if err != nil {
        return fmt.Errorf("failed to read config: %w", err)
    }
    // Process data...
    return nil
}

Multiple Error Wrapping (Go 1.20+)

func multipleErrors() error {
    err1 := errors.New("first error")
    err2 := errors.New("second error")
    
    return fmt.Errorf("multiple failures: %w, %w", err1, err2)
}

Error Inspection

errors.Is

Check if an error matches a target error.
import (
    "errors"
    "io/fs"
)

func handleError(err error) {
    if errors.Is(err, fs.ErrNotExist) {
        fmt.Println("File does not exist")
    } else if errors.Is(err, fs.ErrPermission) {
        fmt.Println("Permission denied")
    } else {
        fmt.Printf("Other error: %v\n", err)
    }
}

func example() {
    _, err := os.Open("nonexistent.txt")
    if err != nil {
        handleError(err)
    }
}

errors.As

Extract a specific error type from an error chain.
import "os"

func examineError(err error) {
    var pathErr *os.PathError
    if errors.As(err, &pathErr) {
        fmt.Printf("Operation: %s\n", pathErr.Op)
        fmt.Printf("Path: %s\n", pathErr.Path)
        fmt.Printf("Error: %v\n", pathErr.Err)
    }
}

errors.Unwrap

Unwrap a wrapped error.
func unwrapExample(err error) {
    unwrapped := errors.Unwrap(err)
    if unwrapped != nil {
        fmt.Printf("Unwrapped: %v\n", unwrapped)
    }
}

Custom Error Types

Simple Custom Error

type ValidationError struct {
    Field string
    Value interface{}
    Msg   string
}

func (e *ValidationError) Error() string {
    return fmt.Sprintf("%s validation failed: %s (value: %v)", e.Field, e.Msg, e.Value)
}

func validateEmail(email string) error {
    if !strings.Contains(email, "@") {
        return &ValidationError{
            Field: "email",
            Value: email,
            Msg:   "must contain @",
        }
    }
    return nil
}

Custom Error with Wrapping

type DatabaseError struct {
    Query string
    Err   error
}

func (e *DatabaseError) Error() string {
    return fmt.Sprintf("database query failed: %s", e.Query)
}

func (e *DatabaseError) Unwrap() error {
    return e.Err
}

func executeQuery(query string) error {
    err := errors.New("connection timeout")
    if err != nil {
        return &DatabaseError{
            Query: query,
            Err:   err,
        }
    }
    return nil
}

Error with Is Support

type NotFoundError struct {
    Resource string
    ID       int
}

func (e *NotFoundError) Error() string {
    return fmt.Sprintf("%s with ID %d not found", e.Resource, e.ID)
}

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

func (e *NotFoundError) Is(target error) bool {
    return target == ErrNotFound
}

func findResource(id int) error {
    return &NotFoundError{Resource: "user", ID: id}
}

func handleNotFound() {
    err := findResource(123)
    if errors.Is(err, ErrNotFound) {
        fmt.Println("Resource not found")
    }
}

Practical Examples

Multi-Error Accumulation

type MultiError struct {
    Errors []error
}

func (m *MultiError) Error() string {
    var messages []string
    for _, err := range m.Errors {
        messages = append(messages, err.Error())
    }
    return strings.Join(messages, "; ")
}

func (m *MultiError) Add(err error) {
    if err != nil {
        m.Errors = append(m.Errors, err)
    }
}

func (m *MultiError) HasErrors() bool {
    return len(m.Errors) > 0
}

func validateUser(user User) error {
    var errs MultiError
    
    if user.Name == "" {
        errs.Add(errors.New("name is required"))
    }
    if user.Email == "" {
        errs.Add(errors.New("email is required"))
    }
    if user.Age < 0 {
        errs.Add(errors.New("age must be positive"))
    }
    
    if errs.HasErrors() {
        return &errs
    }
    return nil
}

Error Chain Walking

func printErrorChain(err error) {
    for err != nil {
        fmt.Printf("Error: %v\n", err)
        err = errors.Unwrap(err)
    }
}

func example() {
    err := fmt.Errorf("outer: %w",
        fmt.Errorf("middle: %w",
            errors.New("inner error")))
    
    printErrorChain(err)
    // Output:
    // Error: outer: middle: inner error
    // Error: middle: inner error
    // Error: inner error
}

Retry with Error Context

func retryOperation(op func() error, maxRetries int) error {
    var lastErr error
    
    for i := 0; i < maxRetries; i++ {
        err := op()
        if err == nil {
            return nil
        }
        
        lastErr = fmt.Errorf("attempt %d/%d: %w", i+1, maxRetries, err)
        time.Sleep(time.Second * time.Duration(i+1))
    }
    
    return fmt.Errorf("operation failed after %d retries: %w", maxRetries, lastErr)
}

Sentinel Errors

var (
    ErrInvalidInput    = errors.New("invalid input")
    ErrUnauthorized    = errors.New("unauthorized")
    ErrResourceExpired = errors.New("resource expired")
)

func processRequest(req Request) error {
    if req.Token == "" {
        return ErrUnauthorized
    }
    if req.Data == nil {
        return ErrInvalidInput
    }
    // Process...
    return nil
}

func handleRequest(req Request) {
    err := processRequest(req)
    
    switch {
    case errors.Is(err, ErrUnauthorized):
        // Handle auth error
    case errors.Is(err, ErrInvalidInput):
        // Handle validation error
    case err != nil:
        // Handle other errors
    }
}

Error with Context Information

type HTTPError struct {
    StatusCode int
    Message    string
    Err        error
}

func (e *HTTPError) Error() string {
    if e.Err != nil {
        return fmt.Sprintf("%d %s: %v", e.StatusCode, e.Message, e.Err)
    }
    return fmt.Sprintf("%d %s", e.StatusCode, e.Message)
}

func (e *HTTPError) Unwrap() error {
    return e.Err
}

func makeRequest(url string) error {
    resp, err := http.Get(url)
    if err != nil {
        return &HTTPError{
            StatusCode: 0,
            Message:    "request failed",
            Err:        err,
        }
    }
    defer resp.Body.Close()
    
    if resp.StatusCode != 200 {
        return &HTTPError{
            StatusCode: resp.StatusCode,
            Message:    "unexpected status",
        }
    }
    
    return nil
}

Error Handling Patterns

Early Return

func processData(data []byte) error {
    if len(data) == 0 {
        return errors.New("empty data")
    }
    
    parsed, err := parse(data)
    if err != nil {
        return fmt.Errorf("parse failed: %w", err)
    }
    
    if err := validate(parsed); err != nil {
        return fmt.Errorf("validation failed: %w", err)
    }
    
    return save(parsed)
}

Error Decoration

func decorateError(operation string, err error) error {
    if err == nil {
        return nil
    }
    return fmt.Errorf("%s failed: %w", operation, err)
}

func workflow() error {
    if err := step1(); err != nil {
        return decorateError("step1", err)
    }
    if err := step2(); err != nil {
        return decorateError("step2", err)
    }
    return nil
}

Panic Recovery with Error

func safeExecute(fn func() error) (err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recovered: %v", r)
        }
    }()
    
    return fn()
}

Best Practices

  1. Use error wrapping - Preserve error context with %w
  2. Create sentinel errors - Define package-level error variables for common errors
  3. Use errors.Is and errors.As - Don’t compare errors with == directly
  4. Provide context - Add information about what was being done when the error occurred
  5. Don’t ignore errors - Always handle or explicitly ignore with _ = err
  6. Custom error types - Create custom types for errors with additional information
  7. Error messages - Start with lowercase, no punctuation at end
  8. Wrap errors once - Don’t wrap the same error multiple times unnecessarily

Common Patterns

Error Variable Naming

var (
    ErrNotFound      = errors.New("not found")
    ErrAlreadyExists = errors.New("already exists")
    ErrInvalidFormat = errors.New("invalid format")
)

Error Type Naming

type ValidationError struct { /* ... */ }
type NetworkError struct { /* ... */ }
type ConfigError struct { /* ... */ }

errors.Join (Go 1.20+)

Combine multiple errors into one.
func processAll(items []Item) error {
    var errs []error
    
    for _, item := range items {
        if err := process(item); err != nil {
            errs = append(errs, err)
        }
    }
    
    return errors.Join(errs...)
}