Background:
Before Go 1.13, an error was simply an interface. To provide additional context for errors, developers often created their own types, but there was no structured mechanism for wrapping errors like in modern Java or C#. With the introduction of Go 1.13, a standard method for error wrapping was introduced through fmt.Errorf and for identifying root causes (errors.Is/errors.As).
Problem:
In complex applications where errors can occur at various levels of the stack, it is crucial not to lose context when returning an error from deep inside the code. Otherwise, debugging becomes difficult and it’s impossible to tell where along the chain the failure occurred.
Solution:
Go allows wrapping errors in new error objects that contain the cause. This is done using %w in fmt.Errorf, and to check underlying causes — errors.Is or errors.As from the errors package.
Code Example:
import ( "errors" "fmt" ) var ErrNotFound = errors.New("not found") func getData() error { return fmt.Errorf("service database: %w", ErrNotFound) } func main() { err := getData() if errors.Is(err, ErrNotFound) { fmt.Println("Detected cause: not found") } else { fmt.Println("Other error:", err) } }
Key Features:
%w for wrapping errors via fmt.Errorf.errors.Is and errors.As.What formatting verb is needed for wrapping errors via fmt.Errorf?
You should use %w, not %v — only %w supports unwrapping.
Code Example:
fmt.Errorf("error: %w", err)
Can you manually create error chains without fmt.Errorf and still detect them using errors.Is?
No, you need to implement the Unwrap interface; otherwise, the standard functions will not unwrap the chain.
Example of the interface:
type wrappedError struct { msg string err error } func (w wrappedError) Error() string { return w.msg + ": " + w.err.Error() } func (w wrappedError) Unwrap() error { return w.err }
Do errors return nil values after wrapping if the underlying error was nil?
No, if the underlying error is nil, the wrapped error is also nil only if used correctly. But if you manually create a wrapper struct where the error field is nil, it will not be nil. This often leads to confusion during checks.
%w, but only %v — losing the ability to trace the chain.Unwrap() for your error structures.errors.Is/As, but only comparing errors directly.In the application, a new error was simply returned with text at each level:
return errors.New("database write error")
Pros:
Cons:
Error wrapping was used with %w and analysis through errors.Is:
if errors.Is(err, ErrNotFound) { return fmt.Errorf("service level error: %w", err) }
Pros:
Cons: