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)) // trueerrors.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