Go’s approach to error handling centers around a built-in interface called error, which requires only an Error() string method. This simplicity enables flexible error modeling but historically offered limited tooling for contextual enrichment or inspection—until Go 1.13 introduced significant enhancements.
Error Interface Basics
The error type is intrinsic to Go, requiring no imports. Any type implementing Error() qualifies as an error. The standard library provides errors.New and fmt.Errorf for basic construction. Internally, errors.New creates an unexported errorString instance:
type errorString struct {
s string
}
func (e *errorString) Error() string { return e.s }
Constructing Errors
Use errors.New for static messages and fmt.Errorf when formatting is needed. Performence-wise, errors.New avoids the overhead of format parsing:
// Faster for static strings
err := errors.New("connection failed")
// Necessary for dynamic content
err := fmt.Errorf("failed to connect to %s:%d", host, port)
Benchmarks show measurable differences when avoiding unnecessary formatting, though real-world impact depends on usage frequency.
Custom Error Types
Structured errors like os.PathError encapsulate context alongside the underlying eror:
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
This pattern preserves original error details while adding operational context.
Chaining Errors in Go 1.13+
Prior to Go 1.13, wrapping errors with fmt.Errorf("context: %v", err) flattened the original error into a string, losing type information. Go 1.13 introduced the %w verb to create chainable errors:
if err != nil {
return fmt.Errorf("database query failed: %w", err)
}
This produces a wrapperError (internal type) that stores both message and wrapped error, implementing a implicit Unwrap() error method.
Unwrapping and Inspection
The errors.Unwrap function retrieves the next error in the chain:
root := errors.Unwrap(err)
For deep inspection, Go provides two key utilities:
errors.Is(err, target): Recursively checks if any error in the chain equalstarget.errors.As(err, &target): Searches the chain for an error assignable totarget's type and assigns it.
These functions respect custom implementations of Is(error) bool or As(interface{}) bool if present.
Upgrading Legacy Code
To adopt chainable errors in existing codebases:
- Replace
%vwith%winfmt.Errorfwhen wrapping. - Use
errors.Isinstead of direct equality checks. - Use
errors.Asinstead of type assertions on error chains. - Implement
Unwrap()in custom error types to participate in chaining. - (Optional) Implement
IsorAsmethods for specialized comparison logic.
Go maintains backward compatibility: old error patterns continue to work, but new code should leverage the enhanced error inspection capabilities for more robust diagnostics.