defer, panic & recover
Prerequisites: Error handling in depth
deferschedules a function call to run when the surrounding function returns — whether it returns normally or because of a panic. It is the standard way to release resources:defer f.Close(),defer mu.Unlock().panicstops normal execution for truly unexpected situations (programmer errors, impossible states). The runtime also panics on things like an out-of-range index or integer division by zero.recover, called inside a deferred function, stops a panic in progress and returns the value that was passed topanic.
Ordinary failures (missing file, bad input) should be returned as error
values, not panics.
Syntax
defer fmt.Println("runs last")
panic("something impossible happened")
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()
Example
package main
import "fmt"
func deferOrder() {
for i := range 3 {
defer fmt.Println("deferred", i)
}
fmt.Println("loop done")
}
func argsEvaluatedEarly() {
x := 1
defer fmt.Println("deferred x =", x) // x is evaluated now, not later
x = 2
fmt.Println("x =", x)
}
// safeDivide turns a runtime panic into an ordinary error.
func safeDivide(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("recovered: %v", r)
}
}()
return a / b, nil
}
func main() {
deferOrder()
argsEvaluatedEarly()
fmt.Println(safeDivide(10, 2))
fmt.Println(safeDivide(1, 0))
fmt.Println("program still running")
}
Expected output
loop done
deferred 2
deferred 1
deferred 0
x = 2
deferred x = 1
5 <nil>
0 recovered: runtime error: integer divide by zero
program still running
Three rules explain this output:
- Deferred calls run in last-in, first-out order.
- A deferred call's arguments are evaluated immediately; only the call itself is delayed.
- A deferred function can read and change named result values — that is
how
safeDividesetserrafter recovering.
Common mistakes
- Using
deferinside a long loop, such asdefer f.Close()for each of thousands of files. Nothing is closed until the function returns. Move the loop body into its own function. - Calling
recover()outside a deferred function, or in a function called by the deferred function — it returnsniland doesn't stop the panic. - Expecting
recoverto catch a panic in a different goroutine. Each goroutine must recover its own panics; an unrecovered panic in any goroutine crashes the whole program. - Using
panic/recoveras a replacement for exceptions in normal control flow. Return errors instead.
Practice
Write mustPositive(n int) int that panics with the message
"negative number" when n < 0 and otherwise returns n. Write
try(n int) that calls it, recovers from any panic, and prints either the
value or recovered: <message>. Call try(5) and try(-1).
Show solution
package main
import "fmt"
func mustPositive(n int) int {
if n < 0 {
panic("negative number")
}
return n
}
func try(n int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()
fmt.Println("value:", mustPositive(n))
}
func main() {
try(5) // value: 5
try(-1) // recovered: negative number
}