Error Handling
Go has no exceptions. An error is just a value — usually the last return value of a function — and callers are expected to check it immediately.
The error interface
Section titled “The error interface”type error interface { Error() string}Any type with an Error() string method satisfies it. The standard library gives you two easy ways to create one:
errors.New("something went wrong")fmt.Errorf("failed to open %s: %w", path, err) // %w wraps another errorThe if err != nil pattern
Section titled “The if err != nil pattern”data, err := os.ReadFile("config.yaml")if err != nil { return fmt.Errorf("reading config: %w", err)}This shows up after nearly every fallible call in Go. It’s more verbose than a try/catch block, but it makes every place an operation can fail visible in the code, rather than hidden in an implicit control-flow jump.
Wrapping and unwrapping
Section titled “Wrapping and unwrapping”%w wraps an error while preserving the original, so callers further up the stack can inspect it with errors.Is and errors.As:
var ErrNotFound = errors.New("not found")
func findUser(id string) error { return fmt.Errorf("findUser(%s): %w", id, ErrNotFound)}
err := findUser("42")if errors.Is(err, ErrNotFound) { // handle the specific case}panic and recover
Section titled “panic and recover”Go does have panic/recover, but they’re reserved for truly unrecoverable situations (programmer bugs, corrupted state) — not for everyday error handling.
func safeDivide(a, b int) (result int, err error) { defer func() { if r := recover(); r != nil { err = fmt.Errorf("recovered: %v", r) } }() return a / b, nil // panics on b == 0}