Wrapping and Inspecting Errors
Overview
Go 1.13 introduced error wrapping, allowing you to add context while preserving the original error for inspection.
Wrapping Errors
originalErr := errors.New("file not found")
// %w wraps the error
wrappedErr := fmt.Errorf("loading config: %w", originalErr)
// The error chain: wrappedErr -> originalErrUnwrapping
err := fmt.Errorf("outer: %w",
fmt.Errorf("middle: %w",
errors.New("inner")))
inner := errors.Unwrap(err) // middle: innererrors.Is
Check if any error in the chain matches:
var ErrNotFound = errors.New("not found")
err := fmt.Errorf("user lookup: %w", ErrNotFound)
if errors.Is(err, ErrNotFound) {
// Handle not found
}errors.As
Extract specific error types:
type ValidationError struct {
Field string
}
func (e *ValidationError) Error() string {
return "validation: " + e.Field
}
err := fmt.Errorf("processing: %w", &ValidationError{Field: "email"})
var valErr *ValidationError
if errors.As(err, &valErr) {
fmt.Println("Invalid field:", valErr.Field)
}Custom Wrappers
type WrappedError struct {
Context string
Err error
}
func (e *WrappedError) Error() string {
return fmt.Sprintf("%s: %v", e.Context, e.Err)
}
func (e *WrappedError) Unwrap() error {
return e.Err
}Best Practices
// Add context when crossing boundaries
func LoadUser(id int) (*User, error) {
data, err := db.Query(id)
if err != nil {
return nil, fmt.Errorf("LoadUser(%d): %w", id, err)
}
return parse(data)
}Summary
| Function | Purpose |
|---|---|
fmt.Errorf("%w", err) |
Wrap error |
errors.Unwrap(err) |
Get wrapped error |
errors.Is(err, target) |
Check chain for match |
errors.As(err, &target) |
Extract typed error |