Golang defer详解,以及常见的一些使用组合的理解。

定义

A defer statement pushes a function call onto a list. The list of saved calls is executed after the surrounding function returns. Defer is commonly used to simplify functions that perform various clean-up actions.

The behavior of defer statements is straightforward and predictable. There are three simple rules:

  • A deferred function's arguments are evaluated when the defer statement is evaluated.
  • Deferred function calls are executed in Last In First Out order after the surrounding function returns.
  • Deferred functions may read and assign to the returning function's named return values.
  • defer的函数参数在defer声明就被求值确定
  • defer的函数调用顺序是先进后出
  • defer的函数可以读取或者赋值具名返回值

函数参数

函数参数被立即求值的情况

package main
import "fmt"
func main() {  
    var i int = 1
    defer fmt.Println("result =>",func() int { return i * 2 }())
    i++
    //prints: result => 2 (not ok if you expected 4)
}

func() int { return i * 2 }()作为参数会被立即求值得到2,然后和函数fmt.Println压入栈,因此输出是2

加上闭包

var whatever [5]struct{}

// part 1
for i := range whatever {
	defer func() { fmt.Print(i) }()
}

// part 2
for i := range whatever {
	defer func(n int) { fmt.Print(n) }(i)
} 

$ 4321044444

由defer先进后出可知,part1输出其实是44444,part2是输出是43210

part1循环里的函数是一个捕获了变量i的闭包,变量i在循环结束后值为4,这里因为闭包变量i也被延长了生命周期。因此最后调用时,所有的defer函数都引用了同一个i,所以结果是4.

part2就比较简单,由于i是defer函数参数,被立即求值并保存,因此结果是43210

返回值

package main

import (
    "fmt"
)

func main() {
    fmt.Println("return:", deferCall1())    // return:0
    fmt.Println("return:", deferCall2())    // return:1
}

func deferCall1() int {
    var i int
    defer func() {
        i++
    }()
    return i*2
}

func deferCall2() (i int) {
    defer func() {
        i++
    }()
    return i*2
}

由于deferCall1是匿名返回值,defer函数无法对返回值赋值,因此结果就是i*2 = 0

deferCall2的执行顺序是这样的:

  1. 执行return i*2语句,相当于i = i*2此时返回值i = = 0
  2. 执行defer里的i++,现在i==1
  3. 最后返回i,即1

defer函数和具名返回值的精确的执行顺序是这样的:

  • return 求值完成,赋值给具名返回值变量
  • defer函数执行,defer可以读取修改具名返回值
  • 函数返回具名返回值

如果defer的函数是变量?

handlers := []func(){
    func() {
    	fmt.Print("A")
    },
    func() {
    	fmt.Print("B")
    },
    func() {
    	fmt.Print("C")
    },
}

for _, h := range handlers {
	defer h()
}

$ CBA

函数本身也是会被立即求值,因为要保存到栈中。

引用

Defer, Panic, and Recover

50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs

How golang's “defer” capture closure's parameter?

How does defer and named return value work in golang?