Defer has some rules, and if you don’t know them, the end result of your code implementation will be different from what you expect. Do you understand these rules?

test

This is the code used for defer, so consider the return value first.

package main

import (
	"fmt"
)

/** * @author: Jason Pang * @description: Snapshot */
func deferFuncParameter1(a) {
	var aInt = 1
	defer fmt.Println(aInt)
	aInt = 2
	return
}

/** * @author: Jason Pang * @description: Snapshot */
func deferFuncParameter2(a) {
	var aInt = 1
	defer func(t int) {
		fmt.Println(t)
	}(aInt)
	aInt = 2
	return
}

/** * @author: Jason Pang * @description: Dynamic */
func deferFuncParameter3(a) {
	var aInt = 1
	defer func(a) {
		fmt.Println(aInt)
	}()
	aInt = 2
	return
}

/** * @author: Jason Pang * @description: Affects the return value * @return ret */
func deferFuncReturn1(a) (ret int) {
	ret = 10
	defer func(a) {
		ret++
		fmt.Println("-- -- -- -- --", ret)
	}()
	return 2
}

/** * @author: Jason Pang * @description: Does not affect the return value * @return ret */
func deferFuncReturn2(a) (ret int) {
	ret = 10
	defer func(ret int) {
		ret++
		fmt.Println("-- -- -- -- --", ret)
	}(ret)
	return 2
}

/** * @author: Jason Pang * @description: defer */
func deferFuncSeq1(a) {
	var aInt = 1
	defer fmt.Println(aInt)
	aInt = 2
	defer fmt.Println(aInt)
	return
}

func main(a) {
	fmt.Println("Snapshot")
	deferFuncParameter1()
	deferFuncParameter2()
	deferFuncParameter3()
	fmt.Println("Return value")
	fmt.Println(deferFuncReturn1())
	fmt.Println(deferFuncReturn2())
	fmt.Println("Order of execution")
	deferFuncSeq1()
}

Copy the code

The correct output is:

➜ myproject go run main.go

The snapshot

1

1

2

The return value

— — — — — 3

3

— — — — — 11

2

Execution order

2

1

Analysis of the

There are a few important rules to defer that can be found in the results above.

As soon as the rule defer is declared, its parameters are parsed in real time

When defer was declared, if the parameters were used directly, they would use the snapshot values and would not change throughout the lifetime. For example deferFuncParameter1 and deferFuncParameter2, the aInt changed after the defer declaration, but the value in defer remains the same.

func deferFuncParameter1(a) {
	var aInt = 1
	defer fmt.Println(aInt)
	aInt = 2
	return
}

func deferFuncParameter2(a) {
	var aInt = 1
	defer func(t int) {
		fmt.Println(t)
	}(aInt)
	aInt = 2
	return
}

Copy the code

On the other end of the spectrum is deferFuncParameter3, which changes with aInt.

func deferFuncParameter3(a) {
	var aInt = 1
	defer func(a) {
		fmt.Println(aInt)
	}()
	aInt = 2
	return
}

Copy the code

Rule 2 defer might manipulate the named return value of the main function

Defer has the potential to change the return value of the function, which is the most error-prone area.

The keyword _return_ is not an atomic operation, in fact _return_ is only a proxy for the assembly instruction _ret_, which is the jump program execution. For example, the return I statement actually takes two steps. The value of I is stored on 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 that the return value will be manipulated during the deferred execution. The execution of return I is as follows:

Result = I Executiondefer
return

Copy the code

So based on this rule, for deferFuncReturn1,

func deferFuncReturn1(a) (ret int) {
	ret = 10
	defer func(a) {
		ret++
		fmt.Println("-- -- -- -- --", ret)
	}()
	return 2
}

Copy the code

The execution process is:

ret = 2
ret++
fmt.Println("-- -- -- -- --", ret)
return

Copy the code

So the final value of RET is 3.

In the case of deferFuncReturn2, since defer declared with parameters directly, we used snapshots and did not affect the return value of RET.

Rule 3 Deferred function execution is executed in last-in, first-out order, meaning that defer comes first and executes last

This rule is familiar, and defer executes in stack order.

Pit instance

Give an example of the mistake of using defer. One of the recommended ways to use transactions in go is to put Rollback into defer and determine if you want to Rollback by checking for an error or panic from the function.

func  Update(a) (resp *baseinfo.Resp, err error) {
	// Start the transaction
	panicked := true
	tx, err := db.TXBegin()
	iferr ! =nil {
		return resp, nil
	}
	defer func(a) {
		ifpanicked || err ! =nil {
			tx.Rollback()
		}
	}()

	/ / update
	err = h.update(shopId, tx)
	iferr ! =nil {// Return on failure
		return resp, nil
	}

	panicked = false
	err = tx.Commit().Error
	iferr ! =nil { // Return on failure
		return resp, nil
	}
	return
}

Copy the code

The err that determines the Rollback is the named return value of the function. In the case of an error, the return value is assigned nil, which means that Rollback will not be performed if there is a failure.

The reason why err is not returned directly and nil is used is because of a framework design problem. Business errors are returned through RESP, and if err is returned directly, the framework will think it is an RPC error.

conclusion

To each knowledge point, need to have accurate understanding, especially in this opportunity to understand, otherwise easy to write a problem.

data

  1. The rules for using defer in Golang

  2. Go expert programming

The last

If you like my article, you can follow my public account (Programmer Malatang)

My personal blog is shidawuhen.github. IO /

Review of previous articles:

  1. Design patterns

  2. recruitment

  3. thinking

  4. storage

  5. The algorithm series

  6. Reading notes

  7. Small tools

  8. architecture

  9. network

  10. The Go