Defer pitfalls
suggest changeWhen using defer
keep the following in mind: deferred functions are called at the end of a function.
Deferred statements have a function scope, not a block scope.
In other words: deferred calls are executed when exiting a function not when executing block created with if
or for
statements.
func main() {
fmt.Print("Before if\n")
if true {
defer fmt.Print("inside if\n")
}
fmt.Print("Ater if\n")
}
Before if
Ater if
inside if
You might expect that deferred statement to be executed when we exit if
branch but it’s executed as the last thing in a function.
Using outside variables in defer functions
func main() {
for i := 0; i < 2; i++ {
defer func() {
fmt.Printf("%d\n", i)
}()
}
}
2
2
A common mistake is to think that this code will print 0
and 1
. Those are the values of i
when we see defer
.
However, defer
creates a closure which only captures variable i
by a reference. It doesn't capture the value of the variable.
It should be become clear when we look at what code executes:
var i int
for i = 0; i < 2; i++ {
// create closure that references i by reference
}
fmt.Printf("%d\n", i) // from created by defer in second loop iteration (remember: reverse order)
fmt.Printf("%d\n", i) // from closure created by defer in first loop iteration
Now it’s clear that when we call deferred fmt.Printf
, i
is 2
.
We can fix this by forcing a capture of the variable:
func main() {
for i := 0; i < 2; i++ {
defer func(i2 int) {
fmt.Printf("%d\n", i2)
}(i)
}
}
1
0
A closure might be slightly more expensive as it requires allocating an object to collect all the variables captured by the closure.