Step 1 of 5 · Reading · ~3 min
Learn
Lifecycle Control
defer, panic, recover
defer: cleanup that cannot be forgotten
defer schedules a call to run when the surrounding function returns — through any path: normal return, early return, even a panic.
Two rules with teeth:
Deferred calls run LIFO — last deferred, first executed. Resources unwind in reverse order of acquisition, which is exactly what you want.
Arguments are evaluated at defer time, not at run time:
If you need the final value, defer a closure that reads the variable: defer func() { fmt.Println(i) }().
panic: the emergency stop
A panic aborts normal control flow and unwinds the stack, running deferred calls as it goes; if nothing stops it, the program crashes with a stack trace. Panics come from bugs — nil dereference, index out of range, and the one in this lesson's exercise: integer division by zero, which panics with runtime error: integer divide by zero.
Idiomatic Go panics rarely and on purpose: for programmer errors ("this can never happen"), not for expected failures. Expected failures are error values — lesson 1.2.
recover: converting a panic into an error
recover() stops the unwinding — but only when called inside a deferred function while a panic is in flight. It returns whatever was passed to panic (or the runtime's error value), or nil if there is no panic.
Here is the whole idiom on a different panic — an out-of-range index — so you can see the shape before you write your own:
Every piece is load-bearing:
- Named results
(v int, err error). By the time the deferred function runs,safeAtis already returning — the only way to change what the caller receives is to assign to a named result variable. With unnamed results there is nothing to assign to, and the caller gets(0, nil). recover()insidedefer func() { ... }(). Callingrecover()in the function body does nothing — no panic is in flight yet.defer recover()does not work either: the spec only stops the panic when recover is called by a deferred function, not as one.- Check
r != nil. The deferred function also runs on the success path; the check keeps you from touching a perfectly good result.
When i is in range, return xs[i], nil completes normally and the defer sees recover() == nil. When it is not, evaluating xs[i] panics, the defer catches it, assigns err, and safeAt returns (0, your error) instead of crashing the program.
Where recover belongs
At boundaries: an HTTP server catching a panicking handler, a library that must not crash its host. Inside ordinary logic, return errors. Recover turns catastrophes into errors at the edge — it is not a try/catch substitute.
Your exercise
Apply that same three-piece pattern to safeDivide(a, b int) (q int, err error): the expression that can panic is a / b, and the error the caller must see carries the message divide by zero. main prints result: <q> on success and error: <message> on failure.
Mistakes the grader will catch:
- Echoing the panic instead of the required message.
err = fmt.Errorf("%v", r)printserror: runtime error: integer divide by zero; the test for7/0expects exactlyerror: divide by zero. - Unnamed results. The panic gets swallowed but
errnever reaches the caller — output isresult: 0, and the test fails. - No recover at all. The program dies with a stack trace; the grader sees a runtime error instead of your output.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…