Errors as Values

Overview

Go treats errors as values, not exceptions. Functions return errors alongside results, making error handling explicit and visible.

The error Interface

type error interface {
    Error() string
}

Returning Errors

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

result, err := divide(10, 0)
if err != nil {
    log.Fatal(err)
}

Creating Errors

import "errors"

// Simple error
err := errors.New("something went wrong")

// Formatted error
err := fmt.Errorf("failed to open %s: %v", filename, originalErr)

Error Handling Patterns

Check Immediately

result, err := doSomething()
if err != nil {
    return err
}
// Use result

Early Return

func process() error {
    data, err := fetch()
    if err != nil {
        return err
    }

    result, err := transform(data)
    if err != nil {
        return err
    }

    return save(result)
}

Add Context

data, err := readFile(path)
if err != nil {
    return fmt.Errorf("loading config: %w", err)
}

Custom Error Types

type ValidationError struct {
    Field   string
    Message string
}

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

func validate(u User) error {
    if u.Name == "" {
        return &ValidationError{Field: "name", Message: "required"}
    }
    return nil
}

Sentinel Errors

var ErrNotFound = errors.New("not found")
var ErrPermission = errors.New("permission denied")

func find(id int) (*User, error) {
    if !exists(id) {
        return nil, ErrNotFound
    }
    // ...
}

// Check
if err == ErrNotFound {
    // Handle not found
}

Summary

Pattern Usage
errors.New() Simple error
fmt.Errorf() Formatted error
Custom type Structured error data
Sentinel Known error values