## 0x00 现象

Go 语言中,`defer` 语句中可以修改函数返回变量,导致返回值被修改。请看下面各种情况:

```go
package main

import "fmt"

// 返回不受 defer 影响,返回 0
func f0() int {
        var i int
        defer func() { i++ }()
        return i
}

// 返回被 defer 修改后的值 1
func f1() (i int) {
        defer func() { i++ }()
        return
}

// 返回引用类型受 defer 影响,返回 [2]
func f2() []int {
        var i = []int{1}
        defer func() { i[0]++ }()
        return i
}

// 指明返回变量 a ,但实际返回的还是 i 。经过 defer 修改后返回 3
func f3() (i int) {
        i = 100 // 在 return 时被重新赋值
        a := 2
        defer func() { i++ }()
        return a
}

// i 在返回前赋值为 3 但不返回,经过 defer 修改,返回 4
func f4() (i int) {
        defer func() { i++ }()
        return 3
}

func main() {
        fmt.Println(f0(), f1(), f2(), f3(), f4()) // 输出:0 1 [2] 3 4
}
```



## 0x01 解释

站内有篇对这一现象说明的[文章]( https://fshex.com/t/724990),经过实际测试( Go 1.22 )得到下面理解:

- **正常返回(匿名返回值)**:函数内部会初始化一个隐藏局部变量储存返回值,在运行到 `return` 时,这个隐藏变量被赋予 `i` 的字面量值。`defer` 语句中修改 `i` 的值不会影响到隐藏返回变量,所以最终返回 `0`。当然,如果 `i` 的类型为引用型(例如切片),那么赋值给隐藏返回变量时,是引用传递,`defer` 语句中的修改依然会体现到返回值上。
- **具名返回(命名返回值)**:要把返回分为三步分析。先给具名返回变量 `i` 赋值,如果 `return` 后带有值(或变量)则赋给 `i`;然后执行 `defer` 语句,里面可能修改 `i` 的值;最后将 `i` 的最终值返回。

经过上面解释,正常 `return` 语句和裸 `return` 语句在逻辑上达成了一致。



## 0x02 疑惑

这里不讨论为什么具名返回值被设计成这样的,我的疑惑是:

- 在延迟语句中,修改返回值的使用场景是什么?
- 承接上一问,如果有使用场景,那么和具名返回配合的使用场景是什么?

如果没有使用场景,我便直接将其理解为「陷阱」。就同 `for` 循环初始化变量只初始化一次一样,小心别用,等待官方改进就是了。

经验尚浅,还望大佬们不吝赐教!
举报· 68 次点击
登录 注册 站外分享
3 条回复  
nagisaushio 小成 2024-8-26 19:14:34
场景:defer 中捕捉到 panic ,转成 err 返回
maocat 小成 2024-8-26 19:14:59
代码出现了 panic ,我需要转成 err 返回
povsister 小成 2024-8-26 19:42:00
一种变相的 catch all 操作
除了 panic 转 err 外,业务上常见对于数据进行返回前检查
将检查条件写入 defer 内,这样可以在一大坨可能好几个月后看不懂的逻辑里少写点 return 赋值。
返回顶部