Lesson 11: Error Handling¶
Big Picture¶
flowchart TD
F["f() returns (result, error)"] --> C{"err != nil?"}
C -->|yes| H["handle or wrap:<br/>fmt.Errorf(ctx: %w, err)"]
C -->|no| U["use result"]
H --> IS["errors.Is → compare sentinel"]
H --> AS["errors.As → extract typed error"]
P["panic"] -.->|"unwinds stack"| R["recover()<br/>inside deferred func"]
note1["error = ordinary VALUE,<br/>not an exception"] --- F
Errors are values, not exceptions. if err != nil is verbose on purpose — it forces you to decide at each failure point. Reserve panic/recover for truly unrecoverable programmer errors.
Concepts¶
Basic Error Handling¶
Creating Errors¶
// Simple error
err := errors.New("something went wrong")
// Formatted error
err := fmt.Errorf("failed to process %s", item)
Custom Error Types¶
type MyError struct {
Code int
Message string
}
func (e *MyError) Error() string {
return e.Message
}
Error Wrapping¶
// Wrap error with context
return fmt.Errorf("operation failed: %w", err)
// Check wrapped errors
if errors.Is(err, SomeError) { }
if errors.As(err, &myErr) { }
Defer and LIFO Order¶
A defer statement schedules a function call to run when the surrounding function is about to return. If a function schedules multiple deferred calls, Go executes them in LIFO (Last In, First Out) order: the last call deferred is the first one run. This stack-like behavior is useful when resources must be cleaned up in the reverse order in which they were acquired.
func deferredCalls() {
defer fmt.Println("first")
defer fmt.Println("second")
defer fmt.Println("third")
}
// Output when deferredCalls returns:
// third
// second
// first
Panic and Recover¶
A deferred function can call recover to stop a panic while the stack is unwinding.
Code Walkthrough¶
package main
import (
"errors"
"fmt"
)
var ErrEmpty = errors.New("empty input")
func parse(s string) (int, error) {
if s == "" {
return 0, fmt.Errorf("parse: %w", ErrEmpty)
}
return len(s), nil
}
func main() {
defer func() {
if r := recover(); r != nil { fmt.Println("recovered:", r) }
}()
if _, err := parse(""); errors.Is(err, ErrEmpty) {
fmt.Println("wrapped sentinel:", err)
}
panic("boom") // unwinds; deferred func recovers
}
Full program: code/11_errors.go.
Exercises
- Define a sentinel
var ErrNotFound = errors.New("not found"), wrap it, then detect it witherrors.Is. - Create a custom error type carrying a status code and extract it with
errors.As. - Write a function that panics, and recover from it in
mainviadefer.
Check Your Understanding¶
What does the %w verb in fmt.Errorf do?
Where can recover() actually catch a panic?