recover只能在同goroutine的defer匿名函数内直接调用,跨goroutine、封装成独立函数或套用其他语言异常思维均无效,且必须用带ok的类型断言处理返回值。

不能靠“语言学习技巧”构建 panic 恢复机制——recover 的行为由 Go 运行时硬性规定,不是靠类比、记忆或修辞技巧能绕过的。 它只在特定上下文中生效:同 goroutine、defer 内、直接调用。任何试图用“类比 Python 异常”或“套用 Java try-catch 思维”的做法,都会导致 recover 永远返回 nil。
recover 必须写在 defer 匿名函数里,不能提取成独立函数
很多人想把 recover 逻辑封装成可复用的工具函数,比如:
func catch() interface{} {
return recover() // ❌ 错误:不在 defer 中,永远 nil
}
func safeRun(f func()) {
defer catch() // 这里只是调用,不是 defer 执行 recover
f()
}这完全无效。recover 的行为依赖于 Go 运行时对当前 goroutine 的 panic 状态快照,该快照只在 defer 关联的函数体执行时存在。
-
recover()必须直接出现在defer func() { ... }()的函数体内部 - 可以复用整个
defer块(比如复制粘贴或用模板生成),但不能把recover()单独拎出来调用 - 哪怕只多包一层普通函数调用,就失去捕获能力
跨 goroutine 的 panic 永远无法被外层 recover 捕获
这是最常踩的坑:把可能 panic 的代码扔进新 goroutine,然后在外层 defer recover —— 结果什么也捞不到。
立即学习“go语言免费学习笔记(深入)”;
例如:
func badSandbox() {
defer func() {
if r := recover(); r != nil {
log.Printf("never reached")
}
}()
go func() {
panic("boom") // 这个 panic 只终止这个 goroutine,不影响外层
}()
}- 每个 goroutine 有独立的 panic 状态,
recover()只对**当前 goroutine** 最近一次未被捕获的 panic 生效 - worker pool 中每个 worker 必须在自己的 goroutine 内完成
defer+recover()闭环 - HTTP handler 封装中,
recover()一定要放在 handler 匿名函数的defer里,而不是 middleware 外层
recover 返回值必须做类型断言,否则可能二次 panic
recover() 返回 interface{},常见类型包括 string、error、自定义结构体。直接打印或比较容易出错:
if r := recover(); r != nil {
log.Printf("%s", r) // ❌ 如果 r 是 error,%s 会 panic
// 正确做法:
if err, ok := r.(error); ok {
log.Printf("error: %v", err)
} else if s, ok := r.(string); ok {
log.Printf("string: %s", s)
} else {
log.Printf("unknown: %v", r)
}
}- 不要假设 panic 值一定是
string或error;第三方库或运行时 panic 可能是其他类型 - 类型断言失败不会 panic,但
r.(error)这种强制断言会 panic;务必用r.(type)或带ok的双值形式 - 如果只是记录日志,用
fmt.Sprintf("%v", r)是安全兜底方式
真正难的不是写对 recover,而是判断哪里该 panic、哪里该返回 error、哪里该加 recover —— 这些决策没法靠“技巧”解决,得靠对业务错误边界的清晰认知和对 goroutine 生命周期的诚实尊重。


















