defer, panic and recover
defer puts a function call aside and runs it as the surrounding function ends. Press Step and watch the deferred line get skipped at first, then run after the last line of main:
1package main2 3import "fmt"4 5func main() {6 fmt.Println("Open the shop")7 defer fmt.Println("Close the shop")8 fmt.Println("Serve customers")9}
Open the shop Serve customers Close the shop
"Close the shop" is written second but shown last. You can write the cleanup right next to the setup, and it still happens at the end.
Last in, first out
Several defers in one function run in reverse order: the last one deferred runs first, like a stack of plates. Press Step to see all three wait until the loop is done:
1package main2 3import "fmt"4 5func main() {6 for i := range 3 {7 defer fmt.Println("deferred", i)8 }9 fmt.Println("loop done")10}
loop done deferred 2 deferred 1 deferred 0
The reverse order makes sense for cleanup: what was opened last is closed first.
Arguments are evaluated right away
What does this show?
1package main2 3import "fmt"4 5func main() {6 count := 17 defer fmt.Println("count at defer:", count)8 count = 109 fmt.Println("count now:", count)10}
The call runs at the end, but its arguments are worked out on the defer line, when count is still 1.
To see the value at the end instead, defer a function with no name that reads the variable when it runs. func() { … }() writes a function on the spot and calls it (lesson 16 has more on these):
1package main2 3import "fmt"4 5func main() {6 count := 17 defer func() {8 fmt.Println("count at the end:", count)9 }()10 count = 1011 fmt.Println("count now:", count)12}
count now: 10 count at the end: 10
Cleanup on every return
A function with several returns would need the cleanup line before each of them. With defer you write it once, and it runs however the function ends:
1package main2 3import "fmt"4 5func withdraw(balance, amount int) int {6 fmt.Println("Open the account")7 defer fmt.Println("Close the account")8 if amount > balance {9 fmt.Println("Not enough money")10 return balance11 }12 return balance - amount13}14 15func main() {16 fmt.Println(withdraw(100, 30))17 fmt.Println(withdraw(100, 300))18}
Open the account Close the account 70 Open the account Not enough money Close the account 100
Look where "Close the account" shows up: before 70. withdraw has to finish (deferred calls included) before main gets the result to show. In real programs defer closes files and unlocks locks (lesson 22).
panic
Some problems a program can't go on from: an index past the end of a slice, a nil map written to. Go panics: it stops the function, runs the deferred calls on the way out, shows a message and ends the program. You can panic yourself, too:
1package main2 3import "fmt"4 5func setPrice(price int) int {6 if price < 0 {7 panic("price can't be negative")8 }9 return price10}11 12func main() {13 defer fmt.Println("deferred in main still runs")14 fmt.Println(setPrice(30))15 fmt.Println(setPrice(-5))16 fmt.Println("never shown")17}
30
deferred in main still runs
panic: price can't be negative
goroutine 1 [running]:
main.setPrice(...)
/tmp/sandbox/prog.go:7 +0x1d
main.main(...)
/tmp/sandbox/prog.go:15 +0x1d
exit status 2What happened: The program called panic (or something it called did): it stops here unless a deferred recover catches it.
First 30. Then setPrice(-5) panics: the deferred line in main still runs, and then Go shows panic: price can't be negative, the call stack (main.setPrice, called from main.main, with line numbers), and exit status 2. "never shown" really isn't shown.
recover
recover() inside a deferred function stops a panic. It returns what was passed to panic (or nil when there's no panic), and the function then returns normally. A common use is to turn a panic into an error:
1package main2 3import "fmt"4 5func safeDivide(a, b int) (result int, err error) {6 defer func() {7 if r := recover(); r != nil {8 err = fmt.Errorf("caught: %v", r)9 }10 }()11 return a / b, nil12}13 14func main() {15 fmt.Println(safeDivide(10, 2))16 fmt.Println(safeDivide(10, 0))17 fmt.Println("still running")18}
5 <nil> 0 caught: runtime error: integer divide by zero still running
10 / 0 panics. The deferred function catches it and sets the named result err (named results, lesson 5); result stays at its zero value, 0. No panic: text, no exit status 2: the program carries on. recover only works directly inside a deferred function; called anywhere else it just returns nil.
When to panic
Hardly ever. Bad input, a missing file, not enough money: these are things that happen, and the caller should decide what to do, so return an error (lesson 14). A panic is for "this can't happen unless the program itself has a bug". Even then, prefer an error when you can.
| Situation | Use |
|---|---|
A user typed abc instead of a number | return an error |
| A key isn't in the map | v, ok := m[k] or an error |
| A case the code should never reach | panic |
| A panic that mustn't stop the whole program | recover in a deferred function |
Practice
Write the code, then press Check. A hint costs nothing; the answer is there when you need it.
Count down with defer
✓Right now countdown(3) shows Ready, then 1, 2, 3. Change one line so the numbers come out backwards (3, 2, 1) after Ready.
Ready 3 2 1 Ready 2 1
1package main2 3import "fmt"4 5func countdown(n int) {6 fmt.Println("Ready")7 for i := 1; i <= n; i++ {8 fmt.Println(i)9 }10}11 12func main() {13 countdown(3)14 countdown(2)15}
Fix it: the till left open
✓When there isn't enough stock, sell returns early and never shows Close the till. Use defer so the till is closed on both paths.
Open the till Close the till Left: 3 Open the till Only 5 left Close the till Left: 5
1package main2 3import "fmt"4 5func sell(stock, qty int) int {6 fmt.Println("Open the till")7 if qty > stock {8 fmt.Println("Only", stock, "left")9 return stock10 }11 fmt.Println("Close the till")12 return stock - qty13}14 15func main() {16 fmt.Println("Left:", sell(5, 2))17 fmt.Println("Left:", sell(5, 9))18}
Turn a panic into an error
✓safeGet(list, 5) panics: index out of range. Add a deferred function with recover that sets err to fmt.Errorf("caught: %v", r), so the program shows both lines.
30 <nil> 0 caught: runtime error: index out of range [5] with length 3
1package main2 3import "fmt"4 5func safeGet(list []int, i int) (v int, err error) {6 return list[i], nil7}8 9func main() {10 prices := []int{10, 20, 30}11 fmt.Println(safeGet(prices, 2))12 fmt.Println(safeGet(prices, 5))13}
What you learned
defer f()runsf()as the surrounding function ends, however it ends.- Several defers run last in, first out.
- The arguments of a deferred call are evaluated on the
deferline;defer func() { … }()reads variables at the end. panicstops the function, runs the deferred calls and ends the program withpanic: …andexit status 2.recover()inside a deferred function stops a panic; with named results it can turn it into an error.- Return errors for things that can happen; keep
panicfor bugs.