recover只能捕获同goroutine中已注册的defer内的panic,必须在panic前声明defer且recover写在其中;跨goroutine无效,返回值需类型断言,不可替代error处理。

recover 不能“兜底”所有 panic,它只在当前 goroutine 中、且必须提前注册好 defer 才能生效——用错位置或时机,就完全捕获不到。
recover 必须写在 defer 函数内部,且 defer 要在 panic 前注册
这是最常踩的坑:把 recover() 放在普通函数里,或者 defer 写在 panic 之后,它就永远返回 nil。Go 不会回溯找“后来注册”的 defer。
-
panic("boom")一执行,当前函数立刻停止,后续代码(包括还没执行的 defer)全跳过 -
defer必须在 panic 触发前就已入栈,也就是写在 panic 上方(哪怕只差一行) - 常见无效写法:
panic()后再写defer func(){ recover() }()—— 这个 defer 根本不会执行 - 正确顺序示例:
func risky() {
defer func() {
if r := recover(); r != nil {
fmt.Println("caught:", r)
}
}()
panic("oops")
// 这行永远不会运行
}
recover 只对当前 goroutine 有效,跨 goroutine 失效
你在 go func() { panic("x") }() 里触发 panic,主 goroutine 里的 recover() 拿不到任何东西。Go 的 panic/recover 是 goroutine 局部机制,不跨边界。
- 并发场景下,每个 goroutine 需要自己配一套
defer + recover - 想统一处理?得靠 channel 或
sync.Once把错误传出来,不能指望主流程 recover 掉子协程的 panic - 典型误用:
go doWork(); defer func(){ recover() }()—— 主 goroutine 的 defer 对子 goroutine 的 panic 完全无感
recover 返回值是 interface{},类型断言容易漏判
recover() 返回的是任意类型,但实际中多数 panic 传的是 string 或 error。直接 fmt.Printf("%v", r) 看着像对了,但一旦 panic 传的是自定义 struct 或 nil,不检查类型就可能 panic 二次崩溃。
- 推荐写法:先判断是否为
error,再 fallback 到fmt.Sprint(r) - 特别注意 Go 1.21+:
panic(nil)会触发 runtime panic,recover()返回*runtime.panicnilerror,不是nil - 安全示例:
if r := recover(); r != nil {
switch err := r.(type) {
case error:
log.Error("panic as error:", err)
case string:
log.Error("panic as string:", err)
default:
log.Error("panic unknown type:", fmt.Sprintf("%v", r))
}
}
recover 不是错误处理替代品,别用它掩盖业务逻辑缺陷
Go 的设计哲学很明确:可预期的失败(比如文件不存在、网络超时、参数校验失败)必须用 error 返回;只有真正不可恢复的崩溃(空指针解引用、切片越界、channel 关闭后写入)才该用 panic/recover。
- 用
recover捕获nil指针解引用,只是避免 crash,但没解决为什么会有 nil —— 这属于 bug,该修代码,不该加 recover - HTTP handler 里用
recover捕获 panic 并返回 500 是合理兜底;但在数据库查询函数里用recover吞掉sql.ErrNoRows就是反模式 - 真实项目中,recover 最常见的合理位置:HTTP server 的顶层中间件、RPC 方法入口、长期运行的 worker goroutine 启动处
真正难的不是写对 recover,而是判断「这里到底该 panic 还是该 return error」——这个分寸感,比语法细节重要得多。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











