Let’s talk about some of the nuance surrounding defer
that’s often lost, or simply forgotten.
Defer statements
…
Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller. If a deferred function value evaluates to
nil
, execution panics when the function is invoked, not when the “defer” statement is executed.
Let’s take this point by point.
When execution encounters a defer
red function:
“function value and parameters are evaluated as usual” … “but the actual function is not invoked”
This bites all of us when we’re learning. And some of us it continues to bite for years into the future!
But what does it mean, exactly?
Let’s illustrate with an example.
func foo() {
var i int
defer fmt.Println(i)
i = 10
fmt.Println("foo")
}
What do you think this code will print when executed?
Try it if you like, to see that it prints:
foo
0
Does this surprise you?
The arguments to the deferred fmt.Println
function are evaluated at the time the defer
is encountered during execution, and at that point i == 0
… but the function itself isn’t executed until the surrounding foo
function returns, so after foo
is printed.
Now, what do you think this will print?
func foo() {
var i int
defer func(p *int) { fmt.Println(*p) }(&i)
i = 10
fmt.Println("foo")
}
In this case, we’re passing a pointer to i
to the deferred function. The argument is still evaluated immediately, as before… but in this case, when foo
exits and the anonymous deferred function is executed, the value pointed to by i
has changed, and we get the following output:
foo
10
The function to be deferred is also evaluated immediately, not upon function exit. To demonstrate:
func main() {
defer bar()()
fmt.Println("Hello, 世界")
}
func bar() func() {
fmt.Println("a")
return func() {
fmt.Println("b")
}
}
This will output the following:
a
Hello, 世界
b
And the final point: If the function being deferred evaluates to nil
, it will panic, but after the function returns, not at the point that defer
is encountered:
func main() {
defer bar()()
fmt.Println("Hello, 世界")
}
func bar() func() {
fmt.Println("a")
return nil
}
This code will panic after some output:
a
Hello, 世界
panic: runtime error: invalid memory address or nil pointer dereference
Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)