Skip to content

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.

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.

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:

go

Every piece is load-bearing:

  • Named results (v int, err error). By the time the deferred function runs, safeAt 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 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:

  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.
Up nextPointer Receivers vs Value ReceiversPointers and Methods

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…