Test questions
Defer has some rules, and if you don't understand it, the final result of the code implementation will be inconsistent with expectations. Do you know these rules?
This is about the code used by defer, you can think about the return value first.
package main import ( "fmt" ) /** * @Author: Jason Pang * @Description: Snapshot */ func deferFuncParameter1() { var aInt = 1 defer (aInt) aInt = 2 return } /** * @Author: Jason Pang * @Description: Snapshot */ func deferFuncParameter2() { var aInt = 1 defer func(t int) { (t) }(aInt) aInt = 2 return } /** * @Author: Jason Pang * @Description: Dynamic */ func deferFuncParameter3() { var aInt = 1 defer func() { (aInt) }() aInt = 2 return } /** * @Author: Jason Pang * @Description: affects return value * @return ret */ func deferFuncReturn1() (ret int) { ret = 10 defer func() { ret++ ("-----", ret) }() return 2 } /** * @Author: Jason Pang * @Description: Does not affect the return value * @return ret */ func deferFuncReturn2() (ret int) { ret = 10 defer func(ret int) { ret++ ("-----", ret) }(ret) return 2 } /** * @Author: Jason Pang * @Description: defer order */ func deferFuncSeq1() { var aInt = 1 defer (aInt) aInt = 2 defer (aInt) return } func main() { ("Snapshot") deferFuncParameter1() deferFuncParameter2() deferFuncParameter3() ("Return Value") (deferFuncReturn1()) (deferFuncReturn2()) ("Execution order") deferFuncSeq1() }
The correct output is:
➜ myproject go run
Snapshot
1
1
2
Return value
----- 3
3
----- 11
2
Execution order
2
1
analyze
There are several important rules for defer, and the above results can be found in these rules.
When a defer is declared, its parameters will be parsed in real time.
When defer is declared, if the parameter is directly used, the parameter will use the snapshot value and will not change throughout the life cycle. For example, deferFuncParameter1 and deferFuncParameter2, although aInt is changed after defer declaration, the value in defer will not change again.
func deferFuncParameter1() { var aInt = 1 defer (aInt) aInt = 2 return } func deferFuncParameter2() { var aInt = 1 defer func(t int) { (t) }(aInt) aInt = 2 return }
The opposite is deferFuncParameter3, which changes with the change of aInt.
func deferFuncParameter3() { var aInt = 1 defer func() { (aInt) }() aInt = 2 return }
Rule 2 defer may operate the name return value of the main function
Defer has the potential to change the return value of the function, which is the most likely place to cause errors.
The keyword _return_ is not an atomic operation. In fact, _return_ only proxys the assembly instruction_ret_, and will jump to the program to execute. For example, the statement return i actually proceeds in two steps, that is, the i value is stored in the stack as the return value, and then the jump is performed. The execution time of defer is just before the jump, so there is still a chance to operate the return value when defer is executed. The execution process of return i is as follows:
result = i
Execute defer
return
So based on this rule, for deferFuncReturn1,
func deferFuncReturn1() (ret int) { ret = 10 defer func() { ret++ ("-----", ret) }() return 2 }
The execution process is:
ret = 2
ret++
("-----", ret)
return
So the final value of ret is 3.
For deferFuncReturn2, because the parameters are directly used when defer is declared, the snapshot is used, which will not affect the return value of ret.
Rule 3: Delay function execution is executed in the order of back-in-first-out
That is, the defer that appears first and finally executes
Everyone is familiar with this rule, and defer is executed in the order of the stack.
Pit example
Give an example of using defer incorrectly. When using transactions in go, there is a recommended way to write them: put Rollback in defer, and determine whether the function has an error or panic, to determine whether it wants to rollback.
func Update() (resp *, err error) { //Open transaction panicked := true tx, err := () if err != nil { return resp, nil } defer func() { if panicked || err != nil { () } }() //renew err = (shopId, tx) if err != nil {//Return failed return resp, nil } panicked = false err = ().Error if err != nil { //Return failed return resp, nil } return }
It is judged that the rolled-back err is the named return value of the function. In the event of an error, the return value is assigned to nil, which means that if there is a failure, Rollback will not be executed.
The reason why err is not returned directly, but uses nil is because of the problem of framework design. Business errors are returned through resp. If err is returned directly, the framework will consider it to be an RPC error.
The above is a detailed explanation of some examples of magical rules of Go defer. For more information about Go defer rules, please follow my other related articles!