Defer is a unique mechanism in Go that allows a function to be executed after the completion of the current function, even in the event of a panic. Historically, this is analogous to on-exit constructs, but in Go, it is implemented as a stack of deferred calls. An important nuance is that the parameters of the deferred function are evaluated immediately when the defer statement is declared, not at the time of its actual invocation!
The issue — non-obvious behavior: one might expect that parameters are passed when defer is triggered, but this is not the case. This often leads to bugs when dealing with mutable or external variables.
The solution — always keep in mind that parameters are evaluated immediately, while the effect of the deferred call occurs later.
Example code:
func f() { x := 5 defer fmt.Println(x) x = 10 } // Prints: 5, not 10
Key features:
What will the following code print? Why?
func main() { i := 0 defer fmt.Println(i) i = 1 }
Answer: will print 0. The argument for fmt.Println is preserved immediately at the declaration of defer.
Does changing a variable after the defer declaration affect its value passed to the function?
No, it does not affect — the evaluation occurs at the defer declaration:
defer fmt.Println(x) // Value of x is saved now, not later
Can you create a defer to output the last state of a variable?
Yes, using an anonymous function (closure):
defer func() { fmt.Println(x) }() // will capture the current value of x at the time of deferred call
The code is invoked like this:
var f *os.File // ... defer f.Close()
But f is assigned later, leading to a panic from a nil pointer call!
Pros:
Cons:
Wrapping the cleanup action through an anonymous deferred function with a check:
defer func() { if f != nil { f.Close() } }()
Pros:
Cons: