GGistDev

Errors in Go

Errors are values. Handle them explicitly and return them up the call stack with context.

Basics

error is an interface type with a single Error() string method. Functions return (T, error) when failures are possible. The zero value for error is nil (no error).

package main
import (
    "errors"
    "fmt"
)

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

func main() {
    v, err := divide(10, 2)
    if err != nil { fmt.Println("err:", err); return }
    fmt.Println(v)
}

Wrapping with context

Wrap errors with %w using fmt.Errorf to preserve the original cause while adding context. This enables unwrapping with errors.Is/As.

package main
import (
    "errors"
    "fmt"
    "strconv"
)

func parseInt(s string) (int, error) {
    v, err := strconv.Atoi(s)
    if err != nil {
        return 0, fmt.Errorf("parse %q: %w", s, err) // wrap
    }
    return v, nil
}

func main() {
    if _, err := parseInt("x"); err != nil {
        fmt.Println("failed:", err)
    }
}

Unwrapping: Is and As

Use errors.Is to test against sentinels and errors.As to extract a specific error type from a wrapped chain.

package main
import (
    "errors"
    "fmt"
    "os"
)

func readConfig(path string) error {
    _, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("open config: %w", err)
    }
    return nil
}

func main() {
    err := readConfig("no-such-file")
    if errors.Is(err, os.ErrNotExist) {
        fmt.Println("missing file")
    }
}

Temporary vs permanent errors

Model retryable conditions with typed errors or wrappers; back off and limit retries.

type Temporary interface{ Temporary() bool }

Custom error types

package main
import "fmt"

type ValidationError struct {
    Field, Message string
}

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

func validateEmail(email string) error {
    if email == "" {
        return ValidationError{Field: "email", Message: "required"}
    }
    return nil
}

Sentinel errors

package main
import "errors"

var ErrTimeout = errors.New("timeout")

Use sentinels sparingly; prefer typed errors or context-rich wrapping.

Best practices

  • Always check err; return early
  • Add context with fmt.Errorf("...: %w", err)
  • Prefer typed errors for structured handling; use errors.Is/errors.As
  • Avoid panics in normal flow; use panics only for truly exceptional programmer errors
  • Log at the boundary; return rich errors within libraries
  • Translate external errors (DB, HTTP) into domain errors at your package boundary

Summary

  • Errors are values; handle explicitly
  • Wrap with %w and inspect with Is/As
  • Use custom/typed errors for clarity and better handling