defer有一些规则,如果不了解,代码实现的最终结果会与预期不一致。对于这些规则,你了解吗?
测试题
这是关于defer使用的代码,可以先考虑一下返回值。
1 | package main |
正确输出为:
➜ myproject go run main.go
快照
1
1
2
返回值
—– 3
3
—– 11
2
执行顺序
2
1
分析
defer有几条重要规则,上面的结果都能从这些规则中找到答案。
规则一 当defer被声明时,其参数就会被实时解析
当defer被声明的时候,如果直接使用了参数,此时的参数就会使用快照值,在整个生命周期内不会变化。如deferFuncParameter1、deferFuncParameter2,虽然aInt在defer声明后被变更,但defer里的值不会再变了。
1 | func deferFuncParameter1() { |
与之相反的是deferFuncParameter3,随aInt的变化而变化。
1 | func deferFuncParameter3() { |
规则二 defer可能操作主函数的具名返回值
defer有可能更改函数的返回值,这是最容易导致错误的地方。
关键字return不是一个原子操作,实际上return只代理汇编指令ret,即将跳转程序执行。比如语句 return i ,实际上分两步进行,即将i值存入栈中作为返回值,然后执行跳转,而defer的执行时机正是跳转前,所以说defer执行时还是有机会操作返回值的。return i的执行过程如下所示:
1 | result = i |
所以基于这个规则,对于deferFuncReturn1,
1 | func deferFuncReturn1() (ret int) { |
执行过程为:
1 | ret = 2 |
所以最终ret的值为3。
对于deferFuncReturn2,因为defer声明的时候直接使用了参数,所以使用的是快照,不会影响ret的返回值。
规则三 延迟函数执行按后进先出顺序执行,即先出现的 defer最后执行
这个规则大家都很熟悉,defer按照栈的顺序执行。
坑实例
举一个错误使用defer的实例。在go中使用事务时,有一种推荐写法:将Rollback放到defer中,通过判断函数是否有报错或者panic,来判断是否要回滚。
1 | func Update() (resp *baseinfo.Resp, err error) { |
判断回滚的err正是函数的具名返回值,在有报错的情况下,返回值被赋值为nil,这意味如果有失败,Rollback也不会被执行。
之所以不将err直接返回,而是使用nil,是因为框架设计的问题,业务错误通过resp返回,如果直接返回err,框架会认为是RPC错误。
总结
对每一个知识点,都需要有准确的了解,尤其在本有机会去了解的时候,否则极易写出问题。
资料
- golang中defer的使用规则
- Go专家编程