0 day streak
go / expert
Snippet

Error Chain Inspection with errors.As and Unwrap

Expert Go error handling layers context around a root cause via fmt.Errorf with %w plus custom types that implement Unwrap(). errors.As walks the chain and copies the first matching typed error into the target pointer, exposing fields like Op and Path. errors.Is walks the same chain comparing against sentinel values. Together they let callers branch on either structural metadata or identity without brittle string matching, and without losing the original error.

snippet.go
go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package main
import (
"errors"
"fmt"
"os"
)
type PathError struct {
Op string
Path string
Err error
}
func (e *PathError) Error() string {
return e.Op + " " + e.Path + ": " + e.Err.Error()
}
func (e *PathError) Unwrap() error { return e.Err }
func openConfig() error {
return &PathError{Op: "open", Path: "/etc/app.conf",
Err: fmt.Errorf("stat: %w", os.ErrNotExist)}
}
func main() {
err := openConfig()
var pe *PathError
if errors.As(err, &pe) {
fmt.Println("op:", pe.Op, "path:", pe.Path)
}
if errors.Is(err, os.ErrNotExist) {
fmt.Println("missing — using defaults")
}
}
Breakdown
1
func (e *PathError) Unwrap() error { return e.Err }
Implementing Unwrap on a pointer receiver hooks PathError into the errors.Is/As traversal so wrapped causes remain inspectable.
2
Err: fmt.Errorf("stat: %w", os.ErrNotExist)
The %w verb wraps the sentinel os.ErrNotExist, producing a two-level chain: PathError → fmt error → os.ErrNotExist.
3
if errors.As(err, &pe) {
errors.As scans the chain and assigns the first *PathError it finds to pe — no type assertion or string parsing needed.
4
if errors.Is(err, os.ErrNotExist) {
errors.Is keeps walking past the PathError layer and matches the deeply wrapped sentinel, enabling clean fallback logic.