recover() 只在 defer 中且同 goroutine 内有效,无法捕获子 goroutine panic 或非 defer 位置的 panic;必须用 defer+recover 包裹可能 panic 的代码,并返回 error 统一处理。

为什么不能直接用 recover() 捕获顶层 panic
Go 的 recover() 只在 defer 函数中且处于发生 panic 的同一 goroutine 时才有效。如果 panic 发生在子 goroutine、或你没在调用栈上设 defer,recover() 就完全不起作用——它会返回 nil,看起来像“捕获失败”,其实是根本没机会运行。
常见误操作是把 recover() 写在普通函数开头,或者试图在另一个函数里“事后补救”,这注定无效。
标准做法:用 defer + recover 包裹可能 panic 的代码块
必须把可能触发 panic 的逻辑(比如第三方库的不安全调用、反射操作、强制类型断言)限制在一个函数作用域内,并在其入口处设置 defer 调用 recover()。
- 函数需返回
(result T, err error)形式,便于统一错误处理 -
recover()返回的是interface{},通常要转成error;建议用fmt.Errorf("panic: %v", r)包装,保留原始值 - 不要忽略
recover()的返回值是否为nil:只有非nil才代表真发生了 panic
func safeJSONUnmarshal(data []byte, v interface{}) error {
defer func() {
if r := recover(); r != nil {
// 这里 r 可能是 string、error 或其他类型
if err, ok := r.(error); ok {
// 第三方库有时直接 panic(error)
panic(err) // 不推荐,但若要透传可重 panic
}
// 统一转为 error
panic(fmt.Errorf("panic: %v", r))
}
}()
return json.Unmarshal(data, v)
}
注意 recover() 无法捕获 runtime panic(如 nil pointer dereference)的细节
recover() 能拿到 panic 值,但拿不到堆栈。比如对 nil 切片调用 append()、或解引用 nil *T,Go 运行时抛出的底层 panic 是未导出的内部结构,recover() 拿到的只是一个字符串描述(如 "runtime error: invalid memory address or nil pointer dereference"),没有文件行号。
- 这类 panic 更适合靠静态检查(
go vet)、单元测试覆盖边界 case 来预防 - 如果非要记录位置,得配合
debug.PrintStack()在recover()后手动打印,但它输出到 stderr,不适合直接塞进error值 - 不要指望靠
recover()实现“健壮的容错服务”——它只是兜底,不是替代防御性编程
第三方库已有 panic 转 error 封装时,优先复用
有些库(如 github.com/valyala/fastjson)提供 ParseBytes() 等安全接口,内部已处理 panic 并返回 error;而 encoding/json 的 Unmarshal 本身就不 panic,只返回 error——你其实不需要自己包一层。
- 先查文档:该函数是否本就承诺不 panic?比如标准库绝大多数 API 都遵循此约定
- 只对明确文档写“may panic”的函数(如
reflect.Value.Interface()在非法状态下调用)才加 recover - 滥用
recover()会让代码难以调试:原本崩溃能立刻定位,现在静默转成 error,反而掩盖问题
真正需要手动 recover 的场景很少,多数时候是设计或调用方式有问题。别把 panic 当作控制流,它真是异常信号。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











