Notes — Module 8: Error Handling¶
These are your personal study notes. Write freely and honestly. Incomplete notes are fine — they show where your understanding still needs work. Return to this file to add insights as they develop over time.
Module: [[go/8. Error Handling]] Topic: [[go]] Date started: YYYY-MM-DD Status: In progress
Concept Map¶
Sketch how the concepts in this module relate to each other. Fill in the Mermaid diagram.
mindmap
root((Error Handling))
error interface
Error() string method
nil means success
typed-nil trap
creating errors
errors.New static string
fmt.Errorf formatted message
%w wrapping verb
%v opaque no chain
error chains
errors.Unwrap linked list
errors.Is sentinel matching
errors.As type extraction
errors.Join multiple errors Go 1.20
custom error types
struct with Error() string
carry structured data
return as error not *MyError
panic and recover
programmer errors only
defer + recover at boundary
re-panic pattern
never for expected failures
named returns + defer
deferred error decoration
single wrap point
Alternative: draw this on paper, photo it, and link the image here.
Key Insights¶
The "aha moments" — the things that, once understood, made the rest clear. Be specific: "I finally understood X because Y" is more useful than "X makes sense".
- error is just an interface: I finally understood that
erroris nothing special — it is a built-in interface with one method. Any type withError() stringsatisfies it. The same structural typing from [[go/6. Methods and Interfaces]] applies. - %w vs %v changes everything: Using
%vinfmt.Errorfcreates an opaque new error — the chain is cut there. Using%wpreserves the chain soerrors.Isanderrors.Ascan traverse it. This clicked when I sawerrors.Isreturn false on a%v-wrapped sentinel. - Add insights as you discover them
My Understanding¶
Explain the core concepts in your own words, as if teaching them to someone else. If you can't explain it simply, you don't understand it well enough yet.
The error interface¶
Your explanation here
What I'm still unsure about: (e.g., exactly what happens when you return a typed nil as error)
errors.Is vs errors.As¶
Your explanation here
What I'm still unsure about: (e.g., what happens when errors.Join is used — does errors.Is traverse all joined errors?)
Panic and recover¶
Your explanation here
What I'm still unsure about: (e.g., can recover be called from a function called by a deferred function, or only directly in the deferred function?)
Error wrapping with %w¶
Your explanation here
What I'm still unsure about: (e.g., can you wrap multiple errors with a single fmt.Errorf call before Go 1.20?)
Connections to Other Topics¶
How does this module connect to things you already know?
| This module's concept | Connects to | How |
|---|---|---|
| error is an interface | [[go/6. Methods and Interfaces]] | error is satisfied structurally — any type with Error() string qualifies; the same nil-interface subtleties apply |
| if err != nil pattern | [[go/2. Control Flow]] | The init-statement form if err := f(); err != nil is the canonical usage; defer also interacts with named return error decoration |
| Named returns + deferred decoration | [[go/3. Functions]] | Named return values enable deferred functions to modify the error before the function returns |
Questions That Arose¶
Log questions as they appear. Don't stop to answer them now — just capture them. Then move the serious ones to QUESTIONS.md.
- What happens when
recover()is called outside a deferred function — does it panic, return nil, or cause a compile error? → added to QUESTIONS.md as Q001 - Can
errors.Join(nil, nil)return nil, or does it return a non-nil joined error? → needs testing - Does the
errors.Istraversal forerrors.Joincheck breadth-first or depth-first? → might matter for nested joined errors
Code Snippets Worth Remembering¶
Patterns, idioms, or examples that captured something important.
The canonical wrap-and-return pattern¶
func doWork(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("doWork %s: %w", path, err)
}
// ...
return nil
}
Why I'm saving this: This is the single most common error pattern in Go. The function name + operand + %w gives context without leaking internals. The %w preserves the chain.
The recover-at-boundary pattern¶
func safeCall(fn func()) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic: %v", r)
}
}()
fn()
return nil
}
Why I'm saving this: Named return err lets the deferred function set the return value. This is the idiomatic recover-to-error conversion used in middleware and plugin systems.
errors.As vs type assertion¶
// Wrong — fails if the error is wrapped:
pe, ok := err.(*ParseError)
// Right — traverses the chain:
var pe *ParseError
if errors.As(err, &pe) {
// pe is now the *ParseError from anywhere in the chain
}
Why I'm saving this: Forgetting this is the most common errors.As mistake. The target must be a pointer (&pe), and the type you're looking for (*ParseError) is inferred from the type of pe.
What Tripped Me Up¶
Mistakes I made, misconceptions I had, things that confused me more than they should have. Being honest here helps you later.
- Typed-nil trap — I initially thought returning
var e *MyError; return efrom a function returningerrorwould give a nil error. It actually gives a non-nil interface with a nil value. The fix is alwaysreturn nilfor the success case, never return a typed nil. - %w vs %v — I initially used
%veverywhere infmt.Errorfwithout realizing it cuts the error chain. Once I triederrors.Ison a%v-wrapped sentinel and gotfalse, the distinction became concrete.
Summary in My Own Words¶
Write a 3–5 sentence summary of this entire module without looking at any notes. If you can't do this, you need more study time.
Write your summary here after completing the module.
Last updated: YYYY-MM-DD