Day 020 — panic, recover & When to Use Them¶
Month 1 · Week 3 · ⬅ Day 019 · Day 021 ➡ · Journal index
🎯 Learning Objective¶
Understand how panic/recover interact with defer, and develop sound judgment about the narrow situations where they belong (and the many where they don't).
📚 Topics¶
panicunwinding the stack · running deferred funcs ·recoveronly in a deferred func- Converting a panic into an error at a boundary
- Re-panicking ·
recover()returns the panic value - Errors vs panics: ordinary failure vs programmer bug / unrecoverable state
📖 Reading / Sources¶
- Learning Go (Bodner) ch.9 — panic and recover
- Tour of Go — Defer/Panic/Recover blog
- Effective Go — Recover
-
builtin.recover
📝 Notes¶
panicstops normal flow and starts unwinding the stack, running each function's deferred calls on the way up → [[defer-lifo]]. If nothing recovers, the program crashes and prints the panic value + stack trace.recoverstops the unwinding — but only works when called directly inside a deferred function. Anywhere else it returnsniland does nothing.recover()returns the value passed topanic(anany), ornilif there's no active panic. A common pattern: assign it to a named return to convert a panic into anerrorat a package boundary.- Idiomatic rule: use errors for expected failures (bad input, missing file, network) and panic only for programmer bugs / truly unrecoverable states (impossible switch case, nil that "can't" be nil, failed package-init invariant). Don't use panic/recover as exceptions for control flow → [[errors-are-values]].
- The standard library does this:
regexp.MustCompilepanics (programmer gave a bad pattern at startup), whileregexp.Compilereturns an error (runtime input).Must…constructors panic by convention. - A library that does recover should convert to an error at its boundary so panics don't leak to callers; or re-panic (
panic(r)) if the value isn't one it knows how to handle. net/http's server already recovers per-request so one handler's panic won't kill the process — but you should still return errors, not rely on that.- Some panics cannot be recovered cleanly in the same goroutine flow you'd expect — e.g. a panic in another goroutine crashes the whole program unless that goroutine recovers. Each goroutine must guard itself → [[goroutines]].
💻 Code Examples¶
// safeDivide converts a panic into an error using a deferred recover
// assigned to a NAMED return value.
func safeDivide(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r) // turn the panic into an error
}
}()
return a / b, nil // a/0 panics with "integer divide by zero"
}
func main() {
r, err := safeDivide(10, 0)
fmt.Println(r, err) // 0 recovered: runtime error: integer divide by zero
}
Full code:
examples/month-01/panic-recover/main.go· Run:go run ./examples/month-01/panic-recover
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Must-style wrapper that panics on error |
✅ | examples/month-01/panic-recover |
Boundary recover → return error |
✅ | examples/month-01/panic-recover |
🐛 Mistakes Made¶
- Called
recover()directly in the function body (not inside a deferred func) — it returnedniland the panic still propagated. - Recovered, logged, and swallowed a panic that was actually a real bug — hid the problem. Re-panicked instead.
❓ Open Questions¶
- Where exactly should a server boundary recover? (At the top of each request/goroutine, converting to a 500 + logged error — never deeper.)
🧠 Active Recall (answer without looking)¶
-
Q: Where must
recover()be called to have any effect?
A
Directly inside a deferred function. Called anywhere else it just returns `nil` and the panic keeps unwinding. -
Q: When should you panic instead of returning an error?
A
Only for programmer bugs / unrecoverable invariants (e.g. `MustCompile` on a bad constant pattern). Expected, runtime failures should be returned as errors.
🪶 Feynman Reflection¶
A panic is the emergency stop: it abandons the current work and runs everyone's cleanup (defer) on the way out the door. recover is the one person standing in a defer who can catch the falling program and decide to turn the crisis into a normal "here's an error" instead. You pull the emergency stop only for genuine emergencies — broken assumptions, not bad user input.
🕳️ Knowledge Gaps¶
- Goroutine-local recovery patterns — will solidify in the concurrency month.
✅ Summary¶
I understand the panic → defer-unwind → recover mechanism, can convert a panic into an error at a boundary, and I reserve panic for programmer bugs while using errors for everything expected.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 021: review the whole week and do closed-book recall.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): panic, recover, and error boundaries (day 020)