1. panic --- 触发程序崩溃
panic 立即中断当前函数的执行,开始逐层退出(unwind),如果没有被 recover 捕获,整个程序崩溃:
bash
package main
import "fmt"
func demonstratePanic() {
fmt.Println("Before panic")
panic("Something went wrong!")
fmt.Println("After panic") // 这行永远不会执行
}
func main() {
fmt.Println("=== demonstratePanic with recovery ===")
func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught panic from demonstratePanic:", r)
}
}()
demonstratePanic()
}()
}
执行结果:
bash
=== demonstratePanic with recovery ===
Before panic
Caught panic from demonstratePanic: Something went wrong!
要点:
panic("Something went wrong!")立即中断函数,"After panic" 不会执行- panic 会触发函数逐层退出(unwind),每一层的 defer 都会执行
- 如果没有任何 recover 捕获,程序崩溃并打印 panic 信息和堆栈追踪
- 本例用匿名函数 + defer/recover 包裹了 demonstratePanic,所以 panic 被捕获,程序继续运行
- panic 的适用场景:不可恢复的错误(如配置缺失、逻辑矛盾),不要用于普通错误处理
2. defer + recover 捕获 panic
recover() 只能在 defer 函数内调用,用于捕获 panic 并恢复程序执行:
bash
package main
import "fmt"
func safeDivide(a, b int) (result int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
result = 0 // 设置 panic 情况下的返回值
}
}()
if b == 0 {
panic("division by zero")
}
return a / b
}
func main() {
// 正常情况
fmt.Println("=== safeDivide normal case ===")
result1 := safeDivide(10, 2)
fmt.Println("Result:", result1)
// panic 情况
fmt.Println("\n=== safeDivide panic case ===")
result2 := safeDivide(10, 0)
fmt.Println("Result:", result2)
}
执行结果:
bash
=== safeDivide normal case ===
Result: 5
=== safeDivide panic case ===
Recovered from panic: division by zero
Result: 0
要点:
recover()返回 panic 传入的值(此处是字符串 "division by zero"),如果没有 panic 则返回nil- recover 只能在 defer 函数内调用,直接调用 recover(不在 defer 中)永远返回 nil
result = 0利用命名返回值result,在 defer 中修改 panic 情况下的返回值- 正常情况:
recover()返回 nil,不进入 if 分支,result保持a/b = 5 - panic 情况:
recover()返回 "division by zero",进入 if 分支,result被设为 0
3. recover 捕获运行时 panic(越界访问等)
recover 可以捕获 Go 运行时产生的 panic,如数组越界、nil 指针等:
bash
package main
import "fmt"
func processArray(arr []int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from array access panic")
}
}()
value := arr[10] // 如果 arr 长度 < 11,运行时 panic
fmt.Println("Value at index 10:", value)
}
func main() {
// 短数组:越界 panic
fmt.Println("=== processArray with short array ===")
shortArr := []int{1, 2, 3}
processArray(shortArr)
// 长数组:正常访问
fmt.Println("\n=== processArray with long array ===")
longArr := make([]int, 15)
for i := 0; i < 15; i++ {
longArr[i] = i * 10
}
processArray(longArr)
}
执行结果:
bash
=== processArray with short array ===
Recovered from array access panic
=== processArray with long array ===
Value at index 10: 100
要点:
shortArr只有 3 个元素,访问arr[10]导致运行时 panic(index out of range)longArr有 15 个元素,arr[10] = 100(10*10=100),正常输出- recover 不区分 panic 来源:无论是手动
panic()还是运行时错误,都能捕获 - 常见的运行时 panic:数组越界、nil 指针访问、类型断言失败、除零等
4. 嵌套 panic 与 recover --- 只有最内层的 recover 能捕获
panic 只会被最近的(最内层的)recover 捕获,外层的 recover 收到 nil:
bash
package main
import "fmt"
func nestedPanic() {
defer func() {
fmt.Println("Outer recovery:", recover()) // 这里会是 nil
}()
func() {
defer func() {
fmt.Println("Inner recovery:", recover()) // 这里捕获 panic
}()
panic("Inner panic")
}()
}
func main() {
fmt.Println("=== nestedPanic example ===")
nestedPanic()
}
执行结果:
bash
=== nestedPanic example ===
Inner recovery: Inner panic
Outer recovery: <nil>
要点:
- 内层匿名函数的 recover 捕获了 "Inner panic",panic 在这里被恢复
- 外层 nestedPanic 的 recover 收到
<nil>,因为 panic 已经被内层处理了 - 原理:panic 沿调用栈逐层 unwind,遇到第一个 recover 就被捕获并停止 unwind
- 这意味着:一旦 panic 被 recover,更高层的函数感知不到有 panic 发生过
- 类似 try-catch:内层 catch 捕获异常后,外层 catch 不会再收到该异常
5. recover 后程序继续运行
panic 被 recover 捕获后,程序从 recover 所在的 defer 函数返回处继续正常执行:
bash
package main
import "fmt"
func safeDivide(a, b int) (result int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
result = 0
}
}()
if b == 0 {
panic("division by zero")
}
return a / b
}
func processArray(arr []int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from array access panic")
}
}()
value := arr[10]
fmt.Println("Value at index 10:", value)
}
func demonstratePanic() {
fmt.Println("Before panic")
panic("Something went wrong!")
fmt.Println("After panic")
}
func nestedPanic() {
defer func() {
fmt.Println("Outer recovery:", recover())
}()
func() {
defer func() {
fmt.Println("Inner recovery:", recover())
}()
panic("Inner panic")
}()
}
func main() {
result1 := safeDivide(10, 2)
fmt.Println("Result:", result1)
result2 := safeDivide(10, 0)
fmt.Println("Result:", result2)
shortArr := []int{1, 2, 3}
processArray(shortArr)
longArr := make([]int, 15)
for i := 0; i < 15; i++ {
longArr[i] = i * 10
}
processArray(longArr)
func() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Caught panic from demonstratePanic:", r)
}
}()
demonstratePanic()
}()
nestedPanic()
// 所有 panic 都被 recover,程序继续正常运行
fmt.Println("\nProgram continues normally")
}
执行结果:
bash
Result: 5
Recovered from panic: division by zero
Result: 0
Recovered from array access panic
Value at index 10: 100
Before panic
Caught panic from demonstratePanic: Something went wrong!
Inner recovery: Inner panic
Outer recovery: <nil>
Program continues normally
要点:
- 所有的 panic 都被 recover 捕获,程序没有崩溃
- 最后输出 "Program continues normally",证明 recover 后程序继续正常运行
- panic/recover 不是普通的错误处理机制,Go 推荐用
error处理可预期的错误 - panic/recover 适用于:真正不可恢复的场景、库的内部防护、顶层兜底恢复
知识点总结
| 知识点 | 关键概念 |
|---|---|
| panic 触发 | panic(msg) 立即中断函数,panic 后的代码不执行 |
| defer + recover | recover 只能在 defer 内调用,捕获 panic 值并恢复执行 |
| recover 修改返回值 | 利用命名返回值,在 defer 中设置 panic 情况的返回值 |
| 捕获运行时 panic | 数组越界、nil 指针等运行时错误也能被 recover 捕获 |
| 嵌套 panic/recover | panic 只被最内层 recover 捕获,外层 recover 返回 nil |
| recover 后继续运行 | panic 被 recover 后,程序从恢复点继续正常执行 |
| 最佳实践 | 可预期错误用 error,不可恢复错误用 panic/recover 兜底 |