| name | go-error-handling |
| description | Go error handling patterns including wrapping, custom error types, errors.Is/As, and error conventions. Use when handling, creating, or checking errors in Go. |
Go Error Handling
Expert guidance for proper error handling in Go.
Quick Reference
| Operation | Pattern | Example |
|---|
| Wrap with context | fmt.Errorf with %w | fmt.Errorf("opening file: %w", err) |
| Create custom error | struct with Error() | type ValidationError struct {...} |
| Check error type | errors.Is | errors.Is(err, ErrNotFound) |
| Extract error | errors.As | errors.As(err, &validationErr) |
| Sentinel errors | var at package level | var ErrNotFound = errors.New("not found") |
| Ignore errors | Never | Always check err != nil |
What Do You Need?
- Error wrapping - Adding context to errors
- Custom error types - Creating structured errors
- Error inspection - errors.Is, errors.As
- Sentinel errors - Package-level error values
- Error conventions - When to wrap, return, or create
Specify a number or describe your error handling scenario.
Routing
| Response | Reference to Read |
|---|
| 1, "wrap", "context", "fmt.Errorf" | wrapping.md |
| 2, "custom", "type", "struct" | custom-errors.md |
| 3, "check", "errors.Is", "errors.As" | inspection.md |
| 4, "sentinel", "package", "global" | sentinel.md |
| 5, general error handling | Read relevant references |
Critical Rules
- Never ignore errors: Always check err != nil
- Wrap with %w: Use %w to preserve error type for errors.Is
- Wrap at boundaries: Wrap when crossing package boundaries
- Don't wrap twice: Avoid double-wrapping the same error
- Use errors.Is for sentinel: Check if error is a specific value
- Use errors.As for types: Extract and inspect custom error types
Error Handling Patterns
Wrapping Errors
func (s *Service) Process(id string) error {
item, err := s.repo.Find(id)
if err != nil {
return fmt.Errorf("finding item %s: %w", id, err)
}
}
return fmt.Errorf("finding item %s: %v", id, err)
return fmt.Errorf("processing: %w", fmt.Errorf("finding: %w", err))
Custom Error Types
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed for field %s: %s", e.Field, e.Message)
}
func (s *Service) Validate(input Input) error {
if input.Email == "" {
return &ValidationError{
Field: "email",
Message: "is required",
}
}
return nil
}
Error Inspection
if errors.Is(err, ErrNotFound) {
}
var validationErr *ValidationError
if errors.As(err, &validationErr) {
}
if errors.Is(err, ErrNotFound) || errors.Is(err, ErrAccessDenied) {
}
Sentinel Errors
var (
ErrNotFound = errors.New("not found")
ErrAccessDenied = errors.New("access denied")
ErrInvalidInput = errors.New("invalid input")
)
func (r *Repository) Find(id string) (*Item, error) {
return nil, ErrNotFound
}
if err != nil {
if errors.Is(err, ErrNotFound) {
return nil, nil
}
return nil, err
}
When to Wrap vs Return
| Scenario | Action |
|---|
| Crossing package boundary | Wrap with context |
| Internal function | Return as-is |
| Adding retry logic | Don't wrap (check with errors.Is) |
| Adding logging | Log then wrap or return |
| API layer | Wrap for user-friendly messages |
Common Mistakes
| Mistake | Severity | Fix |
|---|
| Ignoring errors | Critical | Always check err != nil |
| Using %v instead of %w | High | Use %w to preserve error type |
| Double wrapping | Medium | Wrap only at boundary |
| Panicking on errors | Critical | Return errors, don't panic |
| Creating strings for errors | Low | Use errors.New() or sentinel |
| Wrapping nil error | Medium | Check err != nil before wrapping |
Reference Index
Success Criteria
Error handling is correct when:
- No errors are ignored (all checked)
- Errors wrapped at package boundaries with %w
- Custom error types for domain-specific errors
- Sentinel errors for expected conditions
- errors.Is used for sentinel checking
- errors.As used for type inspection
- No panic on errors (except in package init/main)