NOTE

1.13 defer

What defer is, why it is used, cleanup patterns, interaction with return values and closures, LIFO ordering, recover, and its historical runtime implementation.

GoCreated Updated 1 min readhistorical

This is a historical learning note and may contain outdated or incomplete understanding.

1. What Is defer

A mechanism that lets a function be executed after the current function finishes. Here, finishing includes both normal termination through return and abnormal termination caused by panic.

2. Why defer Is Needed

Reduce resource leaks.

3. How to Use It

Near the statement that creates a resource, use defer to release the resource.

3.1. Close Resources

func TestDefer(t *testing.T) {
	f,err := os.Open("test.txt")
	// Handle the error first
	if err != nil {
		panic(err)
	}
	// Remember to check whether f is nil
	if f != nil {
		defer f.Close()
	}

}

3.2. defer and return

3.2.1. Execution Order

defer executes before return. The order is:

  • return value = xxx
  • call the deferred function
  • empty return

3.2.2. Example 1

func f() (result int) {
    defer func() {
        result++
    }()
    return 0
}

func TestDefer0(t *testing.T) {
	fmt.Println(f())
}
// output
1
  • Rewrite return 0 as result = 0 and return.
func f() (result int) {
	result = 0  // A return statement is not one atomic call; return xxx is actually an assignment + ret instruction
	func() { // defer is inserted before return, between assigning the return value and the ret instruction
		result++
	}()
	return
}
// output
1

3.2.3. Example 2

func TestDefer1(t *testing.T) {
	fmt.Println(f1())
}

// The difference from the previous example is that the return value is not explicitly named
func f1() int {
	t := 5
	defer func() {
		t = t + 5
	}()

	return t
}

// output
5
  • Rewrite return t as r = t and return.

func f1() (r int) {
     t := 5
     r = t // assignment instruction
     func() {        // defer is inserted between assignment and return; in this example, return value r is never modified
         t = t + 5
     }
     return        // empty return instruction
}

3.2.4. Example 3


func f2() (r int) {
	defer func(r int) {
		r = r + 5
	}(r)
	return 1
}

func TestDefer2(t *testing.T) {
	fmt.Println(f4())
}


// output
1
  • Rewrite return 1 as r = 1 and return.
func f2() (r int) {
     r = 1  // assign the return value
     func(r int) {        // the r modified here was passed by value and does not change the r that will be returned
          r = r + 5
     }(r)
     return        // empty return
}

3.3. Multiple defers Are Pushed onto a Stack in Order

3.3.1. Ordinary Argument Passing

func TestDefer3(t *testing.T) {
	for i := 0; i < 10; i++ {
		defer fmt.Println(i) 
	}

	fmt.Println("exited, executing defer")
}

// output
exited, executing defer
9
8
7
6
5
4
3
2
1
0

Why is it a stack? Functions defined later may depend on resources from earlier ones, so naturally they should execute first; otherwise, if earlier cleanup executes first, the dependencies needed by later functions would be gone.

3.3.2. defer + Closure

If the code above is changed to use a closure, the output is different.

func TestDefer4(t *testing.T) {
	for i := 0; i < 10; i++ {
		defer func() {
			fmt.Println(i)
		}()
	}

	fmt.Println("exited, executing defer")
}

// output
exited, executing defer
10
10
10
10
10
10
10
10
10
10

3.4. defer with recover

If the program encounters an exception, it panics. A panic stops the current application rather than only the current goroutine. If some resource cleanup needs to be performed after a panic, use defer + recover.

func TestDefer5(t *testing.T) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println("program panic. err=", err)
		}
	}()
	panic("program crashed")
}
// output
program panic. err= program crashed

4. Implementation

func TestDefer6(t *testing.T) {
	test()
}

func test() {
	var i = 0
	defer func() {
		i++
	}()
	return
}
  • go tool compile -S
0x0054 00084 (defer1_test.go:13)	CALL	runtime.deferprocStack(SB)
0x0059 00089 (defer1_test.go:13)	TESTL	AX, AX
0x005b 00091 (defer1_test.go:13)	JNE	109
0x005d 00093 (defer1_test.go:16)	XCHGL	AX, AX
0x005e 00094 (defer1_test.go:16)	CALL	runtime.deferreturn(SB)
0x0063 00099 (defer1_test.go:16)	MOVQ	72(SP), BP
0x0068 00104 (defer1_test.go:16)	ADDQ	$80, SP
0x006c 00108 (defer1_test.go:16)	RET
0x006d 00109 (defer1_test.go:13)	XCHGL	AX, AX
0x006e 00110 (defer1_test.go:13)	CALL	runtime.deferreturn(SB)
0x0073 00115 (defer1_test.go:13)	MOVQ	72(SP), BP
0x0078 00120 (defer1_test.go:13)	ADDQ	$80, SP
0x007c 00124 (defer1_test.go:13)	RET
  • Where defer appears, the instruction CALL runtime.deferprocStack(SB) is inserted.
  • Before the function returns, the instruction CALL runtime.deferreturn(SB) is inserted.
// Ordinary function return
add xx SP
return

// Return with a defer statement
call runtime.deferreturn,
add xx SP
return

5. References

Discussion

Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub