Golang Defer的调用顺序

1、在defer语句声明时进行估值

我们都知道go中defer是延时调用,一般在return语句结束后调用。但是如果defer语句中包含变量呢,那变量该如何计算?

看这样一个例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
type (
	su struct {}
)

func (s *su) add(i int) *su {
	fmt.Println(i)
	return s
}

func main() {
	x := su{}
	defer x.add(2).add(3)
	fmt.Println(1)
}

请问该函数的输出是什么呢?1,2,3还是2,13?

其实结果是这样的:

1
2
3
2
1
3

解释下,其实是这样的,在执行代码12行的时候,编译器先进行了x.add(2)的计算,然后进行.add(3)的压栈操作。所以就出现了这样的结果,执行12行时对语句进行了估值,先输出了2,紧接着执行13行,输出1;最后执行defer的出栈操作,输出3

我们在看这样一个例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
type (
	su struct {
		num int
	}
)

func (s *su) add(i int) *su {
	s.num++
	fmt.Println("add", i, s.num)
	return s
}

func inc() int {
	x := &su{num: 0}
	defer x.add(2).add(3)
	fmt.Println("inc", x.num)
	return x.num
}

func main() {
	fmt.Println("main", inc())
}

与上面的例子不同的是,在结构体中增加了一个字段num。可以思考下这种情况的输出。

其结果是:

1
2
3
4
add 2 1
inc 1
add 3 2
main 1

首先输出add 2 1应该没有疑问,因为在执行15行的时候,先对x.add(2)进行了估值;

紧接着输出inc 1,因为在x.add(2)中对num进行了+1操作;

然后执行defer函数.add(3),输出add 3 2,此时num又进行了一次+1操作;

最后执行main函数,输出main 1,为啥不是2呢,因为17行先返回。

那如果做一个小的修改,就inc函数返回su结构体指针,在main函数中查看其num值。应该得到main 2

修改后是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
type (
	su struct {
		num int
	}
)

func (s *su) add(i int) *su {
	s.num++
	fmt.Println("add", i, s.num)
	return s
}

func inc() *su {
	x := &su{num: 0}
	defer x.add(2).add(3)
	fmt.Println("inc", x.num)
	return x
}

func main() {
	fmt.Println("main", inc().num)
}

其输出结果是,符合预期。

1
2
3
4
add 2 1
inc 1
add 3 2
main 2

在文章https://go.dev/blog/defer-panic-and-recover中,描述了defer语句的三个原则,其分别是:

1.A deferred function’s arguments are evaluated when the defer statement is evaluated.

即defer函数中的参数在defer语句声明时已经被估值。

所以像下面的写法,将输出0

1
2
3
4
5
6
func a() {
  i := 0
  defer fmt.Println(i)
  i++
  return
}
  1. Deferred function calls are executed in Last In First Out order after the surrounding function returns.

这个比较好理解,defer函数的顺序满足 后入先出 的原则。

像下面的例子,将输出”3210“:

1
2
3
4
5
func b () {
  for i := 0; i < 4; i++ {
    defer fmt.Print(i)
  }
}
  1. Deferred functions may read and assign to the returning function’s named return values.

defer函数能够读取并对函数的命名返回值赋值

看下面这个例子,其结果是”main 2"

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func inc() (i int) {
	defer func() {
		i++
	}()
	return 1
}

func main() {
	fmt.Println("main", inc())
}

附录:

Defer,Panic,and Recover:https://go.dev/blog/defer-panic-and-recover