defer 不能捕获 panic,需配合 recover 在 defer 匿名函数内调用才有效;命名返回值可被 defer 修改,但后注册的会覆盖先注册的;循环中 defer 引用变量需注意闭包陷阱。
很多人误以为 defer 可以“捕获错误”或“拦截 panic”,其实它只是延迟执行函数,不介入控制流。真正捕获 panic 需要

recover(),且必须在 defer 调用的函数内部调用 —— 否则 recover() 返回 nil。
常见错误现象:defer recover() 直接写、或在顶层函数 defer 里调用但没包在匿名函数中,结果 panic 依然崩溃。
recover() 必须在 defer 的函数体内调用,且该函数必须是 panic 发生时仍在 defer 栈中的活跃 goroutinerecover() 并期望它生效;必须是同一个函数作用域(通常用匿名函数闭包)func riskyOp() {
defer func() {
if r := recover(); r != nil {
log.Printf("recovered from panic: %v", r)
// 这里可记录日志、关闭文件、释放锁等
}
}()
panic("something went wrong")
}
Go 中函数返回值有命名和未命名之分,而 defer 函数可以读写命名返回值 —— 这既是便利,也是隐患。尤其当多个 defer 修改同一命名返回值时,容易覆盖预期错误。
使用场景:数据库事务、文件写入、HTTP handler 中统一处理 error 返回。
func foo() (err error))在函数入口就已声明,defer 可直接赋值修改func foo() error)无法被 defer 修改,除非你显式 return 或通过指针传参defer 按后进先出顺序执行,后注册的 defer 会覆盖先注册的对命名返回值的修改func badExample() (err error) {
defer func() { err = errors.New("defer-1") }()
defer func() { err = errors.New("defer-2") }() // 这个生效,前者被覆盖
return nil // 实际返回 "defer-2"
}
在 for 循环中使用 defer,若直接引用循环变量(如 i 或 v),所有 defer 会共享最后一次迭代的值 —— 这是 Go 闭包的经典坑,不是 defer 特有,但常在此暴露。
典型场景:批量启动 goroutine、批量 defer 关闭多个文件句柄、批量 defer 回滚多个 DB 事务。
for i := 0; i → 输出三个 3
for i := 0; i < 3; i++ {
i := i // 创建新变量绑定
defer fmt.Println(i) // 输出 0, 1, 2
}
defer 不是零成本。每次调用都会产生函数栈帧、参数拷贝和 defer 记录(runtime._defer 结构体),在高频路径(如 tight loop、网络包解析)中需谨慎评估。
关键事实:
return 前(包括 panic 路径)f.Close() 紧跟 f.Write() 后)真正需要 defer 的地方,是那些「可能提前 return」且「必须保证执行」的清理逻辑 —— 比如加锁后必须解锁、打开文件后必须关闭、开启事务后必须回滚/提交。不是所有 cleanup 都值得 defer。