Explicit error handling is the most visible Go idiom, and good wrapping makes failures debuggable.
In Go an error is just a value implementing interface { Error() string }. Functions that can fail return it last, and callers check it immediately:
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("load config: %w", err)
}Handle each error once: either deal with it (retry, fallback, log and continue) or return it with added context. Don't log and return, or you'll see the same failure several times.
Creating errors:
errors.New("msg")for a fixed message. Exported package-level ones are sentinel errors, likeio.EOForvar ErrNotFound = errors.New("not found").fmt.Errorf("parse %q: %w", name, err)adds context and wraps the original.%vwould only copy the text.- A custom type (e.g.
type ValidationError struct{ Field string }with anErrormethod) when callers need structured details.
Inspecting errors — always through the chain of wrapped errors, never by comparing strings:
errors.Is(err, ErrNotFound)reports whether any error in the chain equals the target.errors.As(err, &target)finds the first error in the chain assignable totarget's type and stores it.errors.Join(e1, e2)combines several errors;Is/Assearch all of them.
Error strings are lower-case with no trailing punctuation, since they get prefixed: open config: read file: permission denied. Wrapping with %w makes the wrapped error part of your API, so wrap deliberately.
Example
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("not found")
type ValidationError struct{ Field string }
func (e *ValidationError) Error() string { return "invalid " + e.Field }
func find(id int) error {
if id < 0 {
return &ValidationError{Field: "id"}
}
return fmt.Errorf("find user %d: %w", id, ErrNotFound)
}
func main() {
err := find(7)
fmt.Println(err) // find user 7: not found
fmt.Println(errors.Is(err, ErrNotFound)) // true
var ve *ValidationError
if errors.As(find(-1), &ve) {
fmt.Println("bad field:", ve.Field) // bad field: id
}
}Remember
- Return errors as the last result and check them right away
- Wrap with
fmt.Errorf("context: %w", err) - Use errors.Is for sentinels and errors.As for error types
Watch out for
- Comparing
err.Error()strings or using==on wrapped errors instead of errors.Is.