defer 在函数返回前按后进先出执行,参数注册时求值;循环中需显式复制变量;命名返回值可被 defer 修改;nil 资源需判空再 defer;多资源按逆序清理;defer 不跨 goroutine 生效。

defer 不是“函数返回后才执行”,而是“当前函数即将返回前、按后进先出顺序执行”——这个时机判断错了,后面所有资源释放逻辑都会出问题。
defer 参数在注册时就求值,不是执行时
这是最常踩的坑:你以为 defer 会捕获变量的“最终值”,其实它只抓取语句执行那一刻的值。
-
defer fmt.Println(x)中的x在defer这行被执行时就确定了,后续改x = 100不影响输出 - 想延迟读取最新值?得用闭包:
defer func() { fmt.Println(x) }()(注意末尾没括号) - 循环中直接写
for _, f := range files { defer os.Remove(f) }→ 所有defer都删最后一个f;正确写法是for _, f := range files { f := f; defer os.Remove(f) }
命名返回值会被 defer 修改,但别依赖它
当函数声明为 func foo() (err error),defer 里的匿名函数能读写这个 err 变量本身,而不是返回值副本。
-
return nil实际上是先赋值err = nil,再触发defer,所以defer func() { err = fmt.Errorf("wrapped") }()真的会改变返回结果 - 这种写法隐蔽性强,容易让调用方困惑;除非明确需要包装错误,否则建议把错误处理逻辑放在业务代码里,
defer只做清理 - 如果函数有多个 return 路径,且某条路径没设命名返回值,
defer仍能修改它——但此时变量初始值是零值,容易误判
资源释放必须在非 nil 值上 defer,否则 panic
defer f.Close() 看似稳妥,但如果 f 是 nil(比如 os.Open 失败后没检查错误就 defer),运行时直接 panic:panic: runtime error: invalid memory address or nil pointer dereference。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 务必先检查错误:
f, err := os.Open("x"); if err != nil { return err },再defer f.Close() - 对
io.ReadCloser、*sql.Rows、net.Conn等同理:只有非 nil 才能安全 defer 其Close() - 锁也一样:
mu.Lock()后立即defer mu.Unlock()是安全的,因为sync.Mutex方法允许对零值调用;但自定义结构体若Unlock()里有指针解引用,就得小心
多个 defer 按 LIFO 逆序执行,适合嵌套资源管理
执行顺序不是按代码从上到下,而是反过来——这决定了你得倒着安排清理顺序。
-
defer httpResp.Body.Close()应该写在defer httpClient.Close()之前,确保 body 先关、连接后关 - 打开文件 → 解析 JSON → 写入 DB,对应清理顺序应是:DB transaction rollback → JSON decoder cleanup → file close;所以 defer 语句要按“反向业务流”书写
- 不要指望 defer 能跨 goroutine 生效:它只绑定当前函数栈;启动新 goroutine 后的资源(如子协程里的文件句柄)必须由那个 goroutine 自己负责释放
真正难的不是记住 defer 怎么写,而是每次写的时候都得问一句:这个变量此刻的值是不是我想要的?这个资源此刻是不是已经成功初始化?这个 defer 是在正确的 goroutine 里注册的吗?

















