在 Go 里, panic 可以理解为:程序遇到了一个无法继续正常执行的严重错误,于是直接“崩溃式退出”。
它类似 PHP 里的 throw Exception,但语义更偏向于: 程序出现了不应该发生的问题 。
1. 什么是 panic?
示例: package main func main() { panic("程序出错了") }
运行结果类似: panic: 程序出错了 goroutine 1 [running]: main.main() /path/main.go:4 +0x...
意思是:程序主动触发了 panic,然后退出。
2. 常见会触发 panic 的情况
1)数组 / 切片越界 func main() { arr := []int{1, 2, 3} fmt.Println(arr[5]) }
会 panic: panic: runtime error : index out of range [5] with length 3
2)空指针调用 type User struct { Name string } func main() { var u *User fmt.Println(u.Name) }
会 panic: panic: runtime error: invalid memory address or nil pointer dereference
因为 u 是 nil ,不能访问它的字段。
3)关闭已经关闭的 channel func main() { ch := make(chan int) close(ch) close(ch) }
会 panic: panic: close of closed channel
4)往已经关闭的 channel 发送数据 func main() { ch := make(chan int) close(ch) ch <- 1 }
会 panic: panic: send on closed channel
5)类型断言失败 func main() { var x interface {} = "hello" n := x.(int) fmt.Println(n) }
会 panic: panic: interface conversion: interface {} is string, not int
所以类型断言更安全的写法是: n, ok := x.(int) if ok { fmt.Println(n) } else { fmt.Println("不是 int 类型") }
3. 主动触发 panic
可以用 panic() 主动抛出异常: func check age (age int) { if age < 0 { panic("年龄不能小于 0") } } func main() { checkAge(-1) }
这通常用于: panic("这里不应该发生")
也就是程序进入了一个理论上不该进入的状态。
4. panic 会发生什么?
当 panic 发生后: func main() { fmt.Println("开始") panic("出错了") fmt.Println("结束") }
输出: 开始 panic: 出错了
panic 后面的代码不会继续执行。
5. panic 和 defer 的关系
即使发生 panic,已经注册的 defer 仍然会执行。 func main() { defer fmt.Println("defer 执行了") fmt.Println("开始") panic("出错了") fmt.Println("结束") }
输出: 开始 defer 执行了 panic: 出错了
所以 panic 发生时,Go 会先执行当前函数中已经注册的 defer,然后再继续向上层函数传播。
6. panic 的传播过程 package main import "fmt" func a() { defer fmt.Println("a defer") panic("a 出错了") } func b() { defer fmt.Println("b defer") a() } func main() { defer fmt.Println("main defer") b() }
输出大概是: a defer b defer main defer panic: a 出错了
执行过程: main 调用 b b 调用 a a 发生 panic a 的 defer 执行 panic 传给 b b 的 defer 执行 panic 传给 main main 的 defer 执行 程序崩溃退出
7. recover:捕获 panic
Go 提供了 recover() 来恢复 panic。
注意: recover 必须在 defer 函数里面调用才有效。 package main import "fmt" func main() { defer func() { err := recover() if err != nil { fmt.Println("捕获到 panic:", err ) } }() panic("程序出错了") fmt.Println("这行不会执行") }
输出: 捕获到 panic: 程序出错了
程序不会继续崩溃。
8. recover 的经典封装 func safeRun() { defer func() { if err := recover(); err != nil { fmt.Println("捕获 panic:", err) } }() panic("发生错误") } func main() { safeRun() fmt.Println("程序继续执行") }
输出: 捕获 panic: 发生错误 程序继续执行
9. panic 和 error 的区别
Go 里大多数错误应该使用 error,而不是 panic。
error:可预期错误
比如: func findUser(id int) error { if id <= 0 { return errors.New("id 不合法") } return nil }
这种属于业务上可能发生的错误,应该返回 error。
panic:不可预期严重错误
比如: func mustInitConfig() { if config == nil { panic("配置文件加载失败") } }
这种属于程序启动时必须成功的逻辑,如果失败就没必要继续运行。
不会,除非你自己 return
可以用 recover 恢复
参数错误、查询失败、网络失败
数组越界、空指针、程序状态异常
11. Go 推荐的错误处理方式
Go 的习惯是: result, err := doSomething() if err != nil { return err }
而不是: panic(err)
也就是说: 普通错误:return error 严重 bug:panic
package main import ( "errors" "fmt" ) func divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("除数不能为 0") } return a / b, nil } func main() { result, err := divide(10, 0) if err != nil { fmt.Println("错误:", err) return } fmt.Println(result) }
这里应该用 error,不应该用 panic,因为除数为 0 是一个可以预料到的输入错误。
13. 什么时候该用 panic?
一般场景: 1. 程序启动时关键配置加载失败 2. 数据库驱动初始化失败 3. 模板必须解析成功,否则程序无法启动 4. 程序进入了理论上绝不应该进入的分支 5. 测试代码中快速终止
例如: tmpl := template.Must(template.ParseFiles("index. html "))
template.Must() 内部如果发现错误,就会 panic。
相当于: tmpl, err := template.ParseFiles("index.html") if err != nil { panic(err) }
panic 是 Go 里的严重异常机制。
普通业务错误用 error: return err
程序无法继续运行时才用 panic: panic("fatal error")
而 recover 可以在 defer 中捕获 panic,避免程序直接崩溃。
全部评论