defer
Picture a sheet of paper by your front door where, during the day, you note what to do before leaving. You turned on the stove—you add "turn off the stove". You opened a window—you add "close the window". On the way out you read the list from the bottom up: first you close what you opened last.
A function needs the same sheet. A function with several return statements often has to do the same thing on every exit: for example, write a line to a log or close a file. Repeating that line before each return is awkward, and a new return may end up without it.
A deferred call
The word defer in front of a call postpones it until the function exits:
package main
import "fmt"
func check(n int) string {
defer fmt.Println("checked", n) // checked -1
if n < 0 {
return "negative"
}
return "ok"
}
func main() {
fmt.Println(check(-1)) // negative
}A call after defer is called a deferred call—an item on the list by the door. You can defer a call to any function, your own included: defer cleanup().
Let's go through the example step by step:
defer fmt.Println("checked", n)adds the printing to the list but prints nothing yet.- For
-1the conditionn < 0is true, andcheckexits throughreturn "negative". - At that moment the deferred call runs and
checked -1is printed. - Only then does
mainget the result"negative"and print it.
When the call runs
A deferred call runs when the function finishes. The function can exit in two ways:
- through any
return; - after the last line of the body.
A deferred call waits for the whole function to exit, not for the end of an iteration or a block.
defer usually goes right after the action that has to be finished: you open a file, and the next line defers closing it. This keeps the start and the end of the work side by side, and a new return will not separate them.
LIFO order
If there are several deferred calls, they run in reverse order: the last one deferred runs first. This order is called LIFO (last in, first out):
for i := range 3 {
defer fmt.Print(i) // 210
}Here is what happens:
- The loop defers three calls—with
0,1and2. - When
mainexits, the last of them, with2, runs first. - The calls with
1and0follow, and the program prints210.
Common mistake
A deferred call should print the final total, but it prints the starting value:
total := 0
defer fmt.Println("total:", total) // ✗
total += 5The program prints total: 0. The arguments of a deferred call are evaluated at once, on the defer line, and only the call itself is postponed. By the same rule, each deferred call in the loop above remembered its own value of i.
Here the list picture ends. You reread a real sheet as you leave and see what is going on in the house right now. Go, however, stores the item together with its already computed arguments—"print total: 0"—and nobody rewrites them later.
To read the variable at exit, defer a call to a closure:
total := 0
defer func() { fmt.Println("total:", total) }() // total: 5
total += 5Let's take the defer line apart:
- the parentheses after the literal call it:
deferaccepts only a call; - this call has no arguments, so there is nothing to evaluate at once;
- the closure reads
totalwhen it runs, by which time it holds5.
Two rules of defer
Interviewers like to ask this: in what order do deferred calls run, and what values do they print? The answer is the two rules we went through:
- LIFO order: the last deferred call runs first.
- The arguments come from the line where
deferstands.
Key points
deferpostpones a call until the function exits—through anyreturnor after the end of the body.- Deferred calls run in LIFO order: the last one deferred runs first.
- The arguments of a deferred call are evaluated at once, on the
deferline. - To read a variable at exit, defer a call to a closure:
defer func() { … }().
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.