Basic Concepts
What are the characteristics of the deferred statement defer in Go language? When is it usually used?
The deferred statement(defer statement) in Go language has the following characteristics:
- Deferred Execution: Deferred statements are executed before the function containing them exits, regardless of whether the function returns normally or encounters an exception.
- Last In, First Out (LIFO): If there are multiple deferred statements, they are executed in the order of last in, first out (LIFO). In other words, the last deferred statement is executed first, and the first deferred statement is executed last.
Deferred statements are usually used in the following situations:
- Resource Release: Deferred statements can be used to release resources such as open files, close database connections, release locks, etc., before the function returns, to ensure the proper release of resources and avoid resource leaks.
- Error Handling: Deferred statements can be used to handle errors that may occur during the execution of a function. By setting deferred statements at the beginning of the function and checking for errors and handling them accordingly before the function returns, the logic of error handling can be simplified.
- Logging: Deferred statements can be used to log or perform other debugging operations before the function returns, to collect relevant information during the function execution.
The use of deferred statements can improve the readability and maintainability of the code, while ensuring that resource release and cleanup operations are performed in reverse order. It is a common programming technique in Go language for handling resource management and error handling scenarios.
Avoiding Pitfalls
In actual development, the use of defer is not as simple as described above. If defer is not used properly, it can lead to pitfalls.
I'll take you on a journey to avoid pitfalls from two perspectives:
- Firstly, let's break down the execution of deferred statements, paying attention to the fact that the return statement in Go is not atomic;
- Secondly, I'll focus on sharing with you the difference between using anonymous functions and non-anonymous functions after the defer statement.
Breaking Down Deferred Statements
The key to avoiding pitfalls is to deeply understand the following statement:
return xxx
After compilation, the above statement actually generates three instructions:
- Assigning the return value to xxx.
- Invoking the deferred function.
- An empty return.
Steps 1 and 3 are the instructions generated by the return statement, which means return is not an atomic instruction;
Step 2 is the statement defined by defer, where operations on the return value may occur, thereby affecting the final result.
Let's look at two examples and try to break down the return statement and defer statement into the correct order.
First Example
func f()(r int){
t:=5
defer func(){
t=t+5
}()
return t
}
Post breakdown
func f()(r int){
t:=5
// 1. assignment
r=t
// 2. defer in between assigned and empty return
func(){
t=t+5
}()
// 3. empty return
return
}
The return value will be 5.
Second example
func f()(r int){
defer func(r int){
r=r+5
}(r)
return 1
}
Post breakdown
func f() (r int) {
// 1.assignment
r=1
// 2. defer function in between assignment and empty return
func(r int) {
r=r+5
}(r)
// 3. empty return
return
}
In the second step, what changes is the value passed into r, which is a copy of the parameter, and it won't affect the actual parameter r. Therefore, in the main function, calling f()
will return 1.
Deferred Anonymous Functions
When using anonymous functions and non-anonymous functions as arguments to defer, the main difference lies in the passing of function parameters and the scope of their effects:
- Anonymous functions as defer arguments: Anonymous functions can be directly defined within the defer statement, can access variables from the outer function, and will use the current variable values when executed. This approach allows for convenient use of external variables within defer statements, but it's essential to note that variable values may have changed by the time of execution.
- Non-anonymous functions as defer arguments: Non-anonymous functions need to be defined beforehand and then passed as arguments to defer. During execution, the function's current parameter values will be used. This approach allows for the use of pre-defined functions within defer statements, but attention should be paid to function parameter passing and scope.
The reason for this difference lies in the distinctions between anonymous and non-anonymous functions in terms of their definition and scope. Anonymous functions can be directly defined within defer statements and can access variables from the outer function, while non-anonymous functions need to be defined beforehand and then passed as arguments. This design flexibility allows developers to choose the appropriate method of using defer statements based on specific requirements.
For example,
When using an anonymous function as an argument to defer, you can directly define the anonymous function within the defer statement and access external variables.
Below is an example code:
package main
import "fmt"
func main() {
x := 10
defer func() {
fmt.Println("Deferred anonymous function:", x)
}()
x = 20
fmt.Println("Before return:", x)
}
In the example above, an anonymous function is used as an argument to defer, allowing it to access the external variable x.
Before the function returns, the anonymous function within the defer statement will execute and print the value of x.
The output result is as follows:
Before return: 20
Deferred anonymous function: 20
When using a non-anonymous function as an argument to defer, it's necessary to define the function beforehand and then pass the function name as an argument to defer.
Below is an example code:
package main
import "fmt"
func main() {
x := 10
defer printX(x)
x = 20
fmt.Println("Before return:", x)
}
func printX(x int) {
fmt.Println("Deferred function:", x)
}
In the example above, the function printX
is passed as an argument to defer, and the function is defined after the main function.
Before the function returns, the printX
function within the defer statement will execute and print the value of the parameter x passed to it. The output result is as follows:
Before return: 20
Deferred function: 10
Summary
Through the above examples, we can clearly see the difference between using anonymous functions and non-anonymous functions as arguments to defer.
Anonymous functions can be directly defined within defer statements and can access external variables, while non-anonymous functions need to be defined beforehand and then passed as arguments.