在 Go 里面,defer简直是随处可见。最基本的描述就是:它能让函数在返回前执行,而且是“后进先出(LIFO)”。
1. 为什么非要“后进先出”?
defer像栈,所以是反着的。但这只是实现方式,不是设计原因。
设计成 LIFO 的本质原因是:资源依赖。
想象一下这个场景:
- 你先开了一个数据库连接
conn。 - 紧接着,你基于这个
conn开启了一个事务tx。
这时候,如果你要释放资源,肯定得先才关事务,再关连接。如果先关了连接,事务的关闭可能就会因为找不到连接而报错。所以,defer必须保证最后注册的资源最先被释放,这样才能安全地拆掉那些有依赖关系的逻辑。
2. return 之后到底发生了什么?
这是最坑人的地方。很多人以为return x就是结束函数了,其实在底层,它被拆成了好几步。
记住这个执行顺序:
1. 赋值:把要返回的值,挪到一个专门放返回值的“小格子”里。
2. 执行 defer:回头去跑那些排好队的defer函数。
3. 走人(RET):带着“小格子”里的东西跳出函数。
这就解释了一个经典问题:
如果你用的是“匿名返回值”(比如func() int),你在defer里怎么改都没用,因为第一步已经把值存好了。
但如果你用的是“命名返回值”(比如func() (res int)),defer里的逻辑是可以直接修改res的,这会直接影响最终结果。
3. 怎么用?
defer它就是一个“保底兜底”的。
- 成对出现:只要你用了
Lock、Open、Connect,下一行立马写defer Unlock、Close。别管中间有多少if err != nil的 return,这行defer就像买了一份保险,雷打不动。 - Panic 也不怕:就算程序崩了(panic),
defer链条依然会执行完。这是你最后捞救数据的机会(配合recover使用)。
4. 避坑
- 参数是“快照”:你在写
defer func(x)的时候,这个x的值在那一刻就被定死了。哪怕后面你改了x,defer跑的时候还是用的老值。 - 别在长循环里狂写 defer:
defer也是要占内存的。如果你在一个跑 100 万次的循环里写defer,这些任务会堆积到循环彻底结束才执行,很容易把内存撑爆。
总结
它通过栈结构解决了资源释放的顺序问题,又通过介入return流程给了我们处理收尾工作的最高权限。