logo头像

待到风起时,扬帆济沧海

Go 中 defer 与 return 之间的迷之执行顺序

偶然间发现了一个有意思的地方:在使用defer时,匿名返回值的函数和命名返回值的函数的返回结果是不一样的。具体见如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//
// defer_test1.go
// Copyright (C) 2019 bryce <pyplgo@gmail.com>
//
// Distributed under terms of the MIT license.
//

package main

import "fmt"

func WithoutNamedReturnValue() int {
var i int
defer func() {
i++
fmt.Println("defer2 in WithoutNamedReturnValu", i)
}()

defer func() {
i++
fmt.Println("defer1 in WithoutNamedReturnValu", i)
}()

return i
}

func WithNamedReturnValue() (j int) {
defer func() {
j++
fmt.Println("defer2 in WithNamedReturnValu", j)
}()

defer func() {
j++
fmt.Println("defer1 in WithNamedReturnValu", j)
}()

return j
}

func main() {
fmt.Println(WithoutNamedReturnValue())
fmt.Println(WithNamedReturnValue())
}

1
2
3
4
5
6
7
8
9
10
//output
// 结果1
defer1 in WithoutNamedReturnValu 1
defer2 in WithoutNamedReturnValu 2
0

//结果2
defer1 in WithNamedReturnValu 1
defer2 in WithNamedReturnValu 2
2

可以看出,命名返回值的函数的返回值被defer修改了。在 Go Tour 中,官方在介绍defer时是这么说的:

A defer statement defers the execution of a function until the surrounding function returns. (翻译:deferred语句将函数的执行推迟到周围函数返回时执行。)

如果按照这个理解,defer应该没有修改函数返回值的可能,因为defer是 until the surrounding function returns 才执行的。然后查阅官方给的补充材料,然后发现defer的执行有以下三个规则:

1
2
3
4
5
1、 A deferred function’s arguments are evaluated when the defer statement is evaluated.(当计算deferred语句时,将计算deferred函数的参数。)

2、 Deferred function calls are executed in Last In First Out order after the surrounding function returns.(延迟函数调用是在周围函数返回后按Last - in - First - Out顺序执行的。)

3、 Deferred functions may read and assign to the returning function’s named return values.(延迟函数可以读取并分配返回函数的指定返回值。)

这个第三点就是出现文中开头代码结果 2 的原因。那么为什么结果 1 中defer没有修改返回值呢?我们可以推测,其实在函数最终返回前,defer函数就已经执行了,在结果 1 中,由于defer函数是对函数内部变量i进行操作,所以没有影响到返回值。在结果 2
中,由于返回值已经被提前声明,所以defer函数能够在return语句对返回值赋值之后,继续对返回值进行操作。

总结一下就是,函数的整个返回过程应该是:

  • return对返回变量赋值,如果是匿名返回值就先声明再赋值;
  • 执行defer函数;
  • return携带返回值返回。

评论系统未开启,无法评论!