Skip to content

13 — Defer, panic and recover

defer is how a Go function promises to do something on the way out — close the file, unlock the mutex, cancel the context — on every path out, a panic included. Its rules are few and each has a trap: the arguments are saved when the defer runs, not when the call does; the calls run last in, first out; a deferred closure can rewrite a named result; recover works only in the deferred function itself; and a defer in a loop waits for the function, not the iteration. Every lesson here measures one of those on Go 1.25, with the exit status wherever the program does not survive.

Read A panic ends the whole program first: it shows what a panic does to a program with goroutines and the deferred recover inside the goroutine that fixes it, and this chapter does not repeat it. Wrap with %w, unwrap with Is is the error wrapping this chapter's deferred closures do.

Lesson The one thing
defer evaluates its arguments now defer fmt.Println(x) prints the x of the defer line, a closure prints the x at exit, and a value receiver is copied at the defer
Deferred calls run last in, first out releases run in the reverse of the acquires — on return, on a panic and on Goexit; os.Exit skips them; since Go 1.14 a plain defer is open-coded, and the compiler will say so
A defer can change a named result return sets the results, then the deferred closure runs: wrap the error once, recover into it, keep a Close error
recover works only in the deferred call a helper's recover and defer recover() return nil; panic(nil) is a *runtime.PanicNilError; a run-time error is a runtime.Error; a re-panic exits 2 with [recovered, repanicked]
A defer in a loop runs at function end 1,000 deferred Close calls hold 1,000 files until the function returns, a helper per iteration holds one, and go vet says nothing
Companion What it holds
The compiler errors expression in defer must be function call, defer discards result of append, recover (built-in) must be called, missing return after a helper that panics — and the fixes
What go vet and gofmt catch lostcancel, unreachable, unusedresult, copylocks — and the mistakes no tool in the distribution catches, a nil rows.Close() among them
Resources the spec sections, the 2010 blog post, the release notes for 1.14, 1.21 and 1.25, the books by chapter and section, and the advice that has gone stale

Planned

  • debug.Stack inside a recover: logging the stack of the panic you caught, and what GOTRACEBACK changes about the one you did not.
  • net/http's recovery of handler panics, and http.ErrAbortHandler for the panic you want it to keep quiet about.
  • t.FailNow and t.Cleanup: testing's use of runtime.Goexit, and when a Cleanup beats a defer in a test.
  • Panics inside sync.Once: sync.Once runs exactly once shows Once forgetting a panic; OnceValue and OnceFunc re-panicking is not covered here.