Skip to content

Step 1 of 5 · Reading · ~4 min

Learn

Lifecycle Control

defer, panic, recover

You will make cleanup run on every exit path with defer, follow what a panic does to the stack, and use recover at a boundary to turn a crash into an error value.

The last two lessons ended goroutines with a bare wg.Done() or close(ch) that every path had to reach. defer removes that burden.

defer runs at function exit, on any path

go

A deferred call runs when its function returns. Three rules decide what you see.

1. Last in, first out, and a loop does not end a defer. A deferred call waits for the end of the function, not the iteration:

go

This prints use a, use b, release b, release a. For per-iteration cleanup, wrap the body in func() { ... }() so each iteration returns on its own: use a, release a, use b, release b.

2. Arguments are evaluated at the defer statement; a closure reads variables later.

go

Output: closure: 2, then argument: 1.

3. A deferred closure can change named results. return 5 first stores 5 in the result, then the deferred calls run and may still assign to it:

go

retries() returns 10.

panic unwinds the stack

A panic stops the current function, runs its deferred calls, then does the same in its caller, and so on up. Unstopped, it ends the program with a stack trace. Panics come from bugs the runtime detects (index out of range, nil map write, nil pointer, x.(string) on a non-string, closing a closed channel) or from panic(v) with any value v.

go

Stdout shows leaving tail, main cleanup; stderr gets panic: runtime error: invalid memory address or nil pointer dereference. Runtime panic wording changes between Go versions, so never compare it as text.

recover stops the unwinding

recover() returns the value given to panic and stops the unwinding, but only when a deferred function calls it directly. The function that deferred it then returns normally, with whatever its named results hold. Outside a panic it returns nil.

go

In nested, note is called by the deferred function, not deferred itself, so its recover() returns nil. defer recover() fails too.

The recovered value is an interface{}; classify it with a type switch. A runtime panic is both a runtime.Error and an error, so the first matching case wins:

go

A panic in one goroutine kills the whole program, and a recover in main cannot see it:

go

Each goroutine must recover its own panic. Recover at boundaries such as a task runner; inside ordinary logic, return errors.

Common mistakes

  • Deferring in the wrong order. Deferring B's cleanup before A's prints close A first, because defers run in reverse.
  • Cleanup as a plain last statement. A panic skips it, so a failed task prints no close lines.
  • case error above case runtime.Error. Every runtime panic is labelled error ... instead of runtime.
  • Recovering without setting the named result. err stays nil and the task prints an empty result: .

Your exercise

Finish run: register the deferred cleanup lines and a deferred recover that classifies the panic value and sets err. The starter already prints the open lines, parses the tasks and prints the results. The most likely slip is the order of the deferred statements.

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…