Skip to content
defer, panic, recover
step 1/5

Reading — step 1 of 5

Learn

~3 min readLifecycle 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.

go

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:

go

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.

The full idiom, and the exact shape this lesson's exercise needs:

go

Every piece is load-bearing:

  • Named results (q int, err error). By the time the deferred function runs, safeDivide is 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() inside defer func() { ... }(). Calling recover() 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 b is nonzero, return a / b, nil completes normally and the defer sees recover() == nil. When b is zero, evaluating a / b panics, the defer catches it, assigns err, and safeDivide 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

Implement safeDivide exactly as above. main prints result: <q> on success and error: <message> on failure.

Mistakes the grader will catch:

  1. Echoing the panic instead of the required message. err = fmt.Errorf("%v", r) prints error: runtime error: integer divide by zero; the test for 7 / 0 expects exactly error: divide by zero.
  2. Unnamed results. The panic gets swallowed but err never reaches the caller — output is result: 0, and the test fails.
  3. 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…