Go 入门(17):panic 与 recover——什么时候 panic,怎么 recover
更新时间:2026-09-01。本文是
languages/go/beginner/入门层第 17 篇,接 错误处理进阶。Go 设计上选择了"错误是值",所以panic只处理真正不可恢复的错误,不用于常规业务错误。
本文要回答的问题
- panic 和 error 有什么区别?
- 什么时候用 panic,什么时候用 error?
- recover 怎么用?在哪里用?
- panic 会发生什么?栈展开是什么意思?
一、panic vs error:Go 设计哲学
| 对比 | error | panic |
|---|---|---|
| 用途 | 预期的、可处理的错误 | 真正不可恢复、程序不能继续的错误 |
| 返回方式 | 函数返回值 | 程序立刻终止(除非 recover) |
| 常规业务错误 | ✅ 应该用 | ❌ 不应该用 |
| 数组越界、空指针 | panic 自动触发 | |
| 文件打不开、参数非法 | 返回 error |
Go 的一句话总结:错误是预期,panic 是灾难。
二、什么时候会触发 panic
1. 代码显式调用 panic(err)
go
if !initialized {
panic("not initialized") // 没初始化,不能继续跑了
}2. Go 运行时自动触发
Go 遇到严重错误自动 panic:
- 数组越界索引
arr[10]但 len(arr)=5 - nil 指针解引用
var p *int; *p - 关闭 nil channel
- 对 nil 接口调用方法
- 在已关闭的 channel 上发送
三、panic 发生时:栈展开
panic 发生后:
- 停止当前函数执行
- 逐级向上回溯栈,调用 defer 函数
- 如果找不到 recover,程序打印 panic 信息和栈跟踪,然后 exit
go
func f() {
defer fmt.Println("f defer")
panic("panic here")
}
func g() {
defer fmt.Println("g defer")
f()
}
func main() {
g()
}输出顺序:
f defer
g defer
panic: panic here
... 栈跟踪 ...
exit status 2defer 顺序是后进先出,所以 f 的 defer 先执行,然后 g 的。
四、recover:阻止程序退出
recover 只能在 defer 函数里用,能捕获 panic,阻止程序退出:
go
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
// 这里可以做清理,或者返回错误
}
}()
panic("oops")
// recover 了,程序不会 exit
}输出:
recovered: oops但 recover 只能捕获同一个 goroutine 里的 panic,不能捕获别的 goroutine 的。
五、recover 实用场景:web 服务器
写 HTTP 服务时,一个请求 panic 不能把整个服务挂了,所以每个请求都 recover:
go
func handler(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// 打日志,返回 500 错误
log.Printf("panic: %v", err)
http.Error(w, "Internal Server Error", 500)
}
}()
// ... 处理请求,可能 panic ...
}这是 recover 最常见的用途:隔离 panic,不影响整个程序。
六、Go 里 panic/recover 的正确用法
不要这么用:
go
// ❌ 错误:业务错误用 panic
func findUser(id int) *User {
if id < 0 {
panic("invalid id") // 这是业务错误,应该返回 error
}
}应该这么用:
go
// ✅ 正确:不可恢复的错误用 panic
func init() {
if !loadConfig() {
panic("failed to load config at startup") // 启动失败,程序不能继续
}
}总结:
- 业务错误 → 返回 error
- 真正不可恢复 → panic
- 库入口/web 处理 → 用 recover 隔离 panic
七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 业务错误用 panic | 调用者必须 recover,否则程序挂 | 业务错误永远返回 error |
| recover 不在 defer 里 | 没用,不捕获 | 只能在 defer 里调用 recover |
| 别的 goroutine panic 不 recover | 程序崩溃 | 每个 goroutine 入口自己 defer recover |
| recover 后不处理错误 | 程序继续跑,状态不对 | 打日志,清理,然后 exit 或返回错误 |
相关与延伸
下一篇:Go 单元测试——testing 包与表驱动测试;panic 导致 goroutine 泄漏,进阶层讲;Go 错误处理哲学和 C++/Java 异常的对比,对应 错误处理。
一句话总结
Go panic 与 recover:Go 用 error 处理预期错误,panic 处理真正不可恢复的错误;panic 触发栈展开,逐级调用 defer;recover 只能在 defer 里用,能捕获 panic 恢复程序,常见于 HTTP 服务隔离单个请求的 panic;业务错误永远不用 panic。