The defer
statement is a very useful feature in Go, allowing a method to be delayed until the method enclosing it returns. In practical applications, defer
can serve the role of try…catch… in other languages and can be used for handling cleanup operations such as closing file handles.
Timing of defer
Execution
A “defer” statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking.
The official Go documentation explains the timing of defer
execution as follows:
- When the function enclosing the
defer
returns - When the function enclosing the
defer
reaches its end - When the goroutine in which it resides panics
Order of defer
Execution
When a method contains multiple defer
statements, defer
pushes the methods to be executed later onto a stack. When defer
is triggered, all the methods on the stack are popped and executed. Therefore, the execution order of defer
is LIFO (Last In, First Out).
Thus, the output of the following code is not 1 2 3, but 3 2 1.
func stackingDefers() {
defer func() {
fmt.Println("1")
}()
defer func() {
fmt.Println("2")
}()
defer func() {
fmt.Println("3")
}()
}
Pitfall 1: Different Behavior of defer
in Anonymous and Named Return Value Functions
First, observe the results of executing the following two methods.
func returnValues() int {
var result int
defer func() {
result++
fmt.Println("defer")
}()
return result
}
func namedReturnValues() (result int) {
defer func() {
result++
fmt.Println("defer")
}()
return result
}
The first method outputs 0, while the second outputs 1. The first method uses an anonymous return value, while the second uses a named return value. Other than that, the logic is the same. Why is there a difference in output?
To understand this, you need to know the execution logic of defer
. The documentation states that the defer
statement is triggered “at” the time of method return, meaning return
and defer
execute “simultaneously”. Using the anonymous return value method as an example, the process is as follows:
- Assign
result
to the return value (you can think of it as Go automatically creating a return valueretValue
, equivalent to executingretValue = result
) - Then check for
defer
, and if present, execute it - Return the previously created return value (
retValue
)
In this case, the modification in defer
is applied to result
, not retValue
, so defer
still returns retValue
. In the named return value method, since the return value is already defined when the method is defined, there is no creation of retValue
. Thus, result
is retValue
, and the modification in defer
is directly returned.
Pitfall 2: Potential Performance Issues Using defer
in a for
Loop
Consider the following code:
func deferInLoops() {
for i := 0; i < 100; i++ {
f, _ := os.Open("/etc/hosts")
defer f.Close()
}
}
defer
is declared immediately after the resource creation statement, and the logic seems fine. However, compared to direct invocation, defer
has additional overhead, such as memory copying for the parameters needed later and pushing and popping the defer
structure on the stack. Therefore, defining defer
in a loop can lead to significant resource overhead. In this example, removing the defer
before the f.Close()
statement can reduce the extra resource consumption caused by numerous defer
statements.
Pitfall 3: Releasing Resources with defer
Only After Checking for err
Some operations to acquire resources may return an err
parameter. While you can choose to ignore the returned err
parameter, if you intend to use defer
for delayed release, you need to check for err
before using defer
. If the resource acquisition fails, there’s no need and it is not advisable to perform a release operation on the resource. Not checking whether the resource was successfully acquired before executing the release operation can lead to errors in the release method.
The correct approach is as follows:
resp, err := http.Get(url)
// First, check if the operation was successful
if err != nil {
return err
}
// If successful, then perform the Close operation
defer resp.Body.Close()
Pitfall 4: defer
Not Executed When Calling os.Exit
When a panic occurs, all defer
statements in the goroutine are executed. However, when the os.Exit()
method is called to exit the program, defer
statements are not executed.
func deferExit() {
defer func() {
fmt.Println("defer")
}()
os.Exit(0)
}
The defer
in the above example will not output anything.