Go 语言中延迟语句修改返回值算陷阱还是特性? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
请不要在回答技术问题时复制粘贴 AI 生成的内容
assassing

Go 语言中延迟语句修改返回值算陷阱还是特性?

  •  
  •   assassing
    hxz393 Aug 26, 2024 2647 views
    This topic created in 615 days ago, the information mentioned may be changed or developed.

    0x00 现象

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

    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 解释

    站内有篇对这一现象说明的文章,经过实际测试( Go 1.22 )得到下面理解:

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

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

    0x02 疑惑

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

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

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

    经验尚浅,还望大佬们不吝赐教!

    9 replies    2024-08-28 10:38:51 +08:00
    nagisaushio
        1
    nagisaushio  
       Aug 26, 2024 via Android   1
    场景:defer 中捕捉到 panic ,转成 err 返回
    maocat
        2
    maocat  
       Aug 26, 2024 via iPhone
    代码出现了 panic ,我需要转成 err 返回
    assassing
        3
    assassing  
    OP
       Aug 26, 2024
    @nagisaushio #1 准,算一例
    povsister
        4
    povsister  
       Aug 26, 2024
    一种变相的 catch all 操作
    除了 panic 转 err 外,业务上常见对于数据进行返回前检查
    将检查条件写入 defer 内,这样可以在一大坨可能好几个月后看不懂的逻辑里少写点 return 赋值。
    assassing
        5
    assassing  
    OP
       Aug 26, 2024
    @povsister #4 可是可以,但那样就不好写测试代码了
    rekulas
        6
    rekulas  
       Aug 27, 2024   1
    算特性吧, 会这样写的大多应该了解这样做会产生什么影响,不至于引起意外 bug
    我很喜欢这特性,当做函数的析构逻辑来用,例如某些复杂方法内部可能包含多个判断,可能会涉及提前返回,不同的返回可能关联了不同的退出逻辑,我就可以在 defer 里统一处理,而不用每个 return 都去调用或者判断
    assassing
        7
    assassing  
    OP
       Aug 28, 2024
    @rekulas #6 请问怎么理解「不同的返回可能关联了不同的退出逻辑」?这是使用 defer 修改返回值的关键。我理解的这种情况,需要后续单独使用一个函数来处理返回值,除非在 defer 中处理能获得相当大的便利性
    rekulas
        8
    rekulas  
       Aug 28, 2024   1
    @assassing 后续单独使用一个函数来处理返回值
    自然也是可以的,但如果你的逻辑依赖的数据是函数内部变量,那你可以选择定义内部函数然后每个退出都调用一次,总归没有 defer 方便,而且可能遗漏导致 bug ,另外有些场景下可能需要对返回值进行二次处理,用 defer 也更方便,不容易出 bug
    举个例子,一个典型场景函数内部开启事务,在 defer 中 commit 就可以避免出现事务未提交阻塞数据库的情况,现在很多 orm 也用这个原理封装了事务函数,尽最大可能降低了 bug 产生率
    assassing
        9
    assassing  
    OP
       Aug 28, 2024
    @rekulas #8 理解了,谢谢!
    About     Help     Advertise     Blog     API     FAQ     Solana     802 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 48ms UTC 21:55 PVG 05:55 LAX 14:55 JFK 17:55
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86