Error Wrapping
Fundamentals Chapter 5 introduced error and nil. In real programs, an error often passes through several layers of function calls before reaching code that decides what to do about it — and each layer usually wants to add context ("failed while loading config" on top of "file not found"). Error wrapping is how Go adds that context without throwing away the original error underneath.
A Sentinel Error — A Known, Comparable Error Value
A sentinel error — a specific, named error value declared once at package level — lets calling code check for that exact error later. This is the foundation everything else in this chapter builds on: a known error value that can be compared against.
Wrapping an Error with fmt.Errorf and %w
%w (used only with fmt.Errorf) wraps the original error inside a new one, attaching extra context ("loadItem: ") while keeping the original — ErrNotFound — retrievable from inside the new error. This differs from %v or %s, which would only produce a plain string with no way back to the original error value.
errors.Is — Checking for a Specific Error Through Any Number of Wraps
err != ErrNotFound (a direct comparison) would actually return true here, since err is now the wrapped version, not the original — direct comparison breaks once wrapping is involved. errors.Is(err, ErrNotFound) unwraps as many layers as necessary, checking whether ErrNotFound is anywhere inside the chain. This is the correct, wrap-aware way to ask "is this ultimately that specific error?"
fmt.Errorf("...: %w", err) even once, it is a brand-new error value — == against the original sentinel will always be false. errors.Is exists specifically to make this comparison correctly regardless of how many wrapping layers exist.
Custom Error Types
Any type with an Error() string method automatically satisfies Go's built-in error interface (Intermediate Chapter 2's structural typing again, applied to a single specific method). A custom error type like ValidationError can carry structured data — Field, Message — rather than just a flat message string.
errors.As — Extracting a Custom Error Type
errors.As checks whether an error (possibly wrapped) matches a specific custom error type, and if so, assigns it into valErr so its specific fields (Field, Message) become accessible — something a plain error interface value alone never allows. errors.Is answers "is this that exact value?"; errors.As answers "is this that type, and if so, give it back to me properly typed."
| Tool | Question it answers |
|---|---|
| fmt.Errorf("...: %w", err) | Add context while preserving the original error |
| errors.Is(err, sentinel) | "Is this ultimately THIS specific error value?" |
| errors.As(err, &target) | "Is this (or something it wraps) of THIS type? Give it to me." |
| err == sentinel | Unsafe once wrapping is involved — avoid |
Coding Challenges
Declare var ErrEmptyName = errors.New("name cannot be empty"). Write a function greet(name string) error that returns this sentinel (wrapped with fmt.Errorf and extra context "greet: %w") if name is "". Use errors.Is to check the result and print an appropriate message.
📄 View solutionDefine a custom error type RangeError with fields Min and Max ints, and an Error() string method describing the valid range. Write a function checkAge(age int) error returning a *RangeError if age is outside 0-120. Use errors.As to extract it and print Min/Max.
📄 View solutionWrite three functions that call each other: layerOne calls layerTwo, layerTwo calls layerThree, and layerThree returns a sentinel error ErrFailed. Each layer wraps the error with its own name using fmt.Errorf("...: %w", err). Print the final error from layerOne, then confirm errors.Is(err, ErrFailed) is still true despite 3 layers of wrapping.
📄 View solutionChapter 2 Quick Reference
- var ErrX = errors.New("...") — a sentinel error, a known comparable value
- fmt.Errorf("context: %w", err) — wraps err, adding context, keeping it unwrappable
- errors.Is(err, sentinel) — true if sentinel appears anywhere in err's wrap chain
- errors.As(err, &target) — extracts a specific custom error TYPE from the chain
- type X struct {} + func (e *X) Error() string — defines a custom error type
- Never use == on a wrapped error — it will not match, even when logically "the same"
- Next chapter: context.Context — cancellation, timeouts, and request-scoped values