3.7 Go panic 与 recover 学习笔记
1. panic — 触发程序崩溃
panic立即中断当前函数的执行,开始逐层退出(unwind),如果没有被 recover 捕获,整个程序崩溃:
package mainimport"fmt"funcdemonstratePanic(){fmt.Println("Before panic")panic("Something went wrong!")fmt.Println("After panic")// 这行永远不会执行}funcmain(){fmt.Println("=== demonstratePanic with recovery ===")func(){deferfunc(){ifr :=recover();r!=nil{fmt.Println("Caught panic from demonstratePanic:", r)}}()demonstratePanic()}()}执行结果:
===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 并恢复程序执行:
package mainimport"fmt"func safeDivide(a, b int)(result int){deferfunc(){ifr :=recover();r!=nil{fmt.Println("Recovered from panic:", r)result=0// 设置 panic 情况下的返回值}}()ifb==0{panic("division by zero")}returna / b}funcmain(){// 正常情况 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)}执行结果:
===safeDivide normalcase===Result:5===safeDivide paniccase===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 指针等:
package mainimport"fmt"func processArray(arr[]int){deferfunc(){ifr :=recover();r!=nil{fmt.Println("Recovered from array access panic")}}()value :=arr[10]// 如果 arr 长度<11,运行时 panic fmt.Println("Value at index 10:", value)}funcmain(){// 短数组:越界 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)fori :=0;i<15;i++{longArr[i]=i *10}processArray(longArr)}执行结果:
===processArray with short array===Recovered from array access panic===processArray with long array===Value at index10: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:
package mainimport"fmt"funcnestedPanic(){deferfunc(){fmt.Println("Outer recovery:", recover())// 这里会是 nil}()func(){deferfunc(){fmt.Println("Inner recovery:", recover())// 这里捕获 panic}()panic("Inner panic")}()}funcmain(){fmt.Println("=== nestedPanic example ===")nestedPanic()}执行结果:
===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 函数返回处继续正常执行:
package mainimport"fmt"func safeDivide(a, b int)(result int){deferfunc(){ifr :=recover();r!=nil{fmt.Println("Recovered from panic:", r)result=0}}()ifb==0{panic("division by zero")}returna / b}func processArray(arr[]int){deferfunc(){ifr :=recover();r!=nil{fmt.Println("Recovered from array access panic")}}()value :=arr[10]fmt.Println("Value at index 10:", value)}funcdemonstratePanic(){fmt.Println("Before panic")panic("Something went wrong!")fmt.Println("After panic")}funcnestedPanic(){deferfunc(){fmt.Println("Outer recovery:", recover())}()func(){deferfunc(){fmt.Println("Inner recovery:", recover())}()panic("Inner panic")}()}funcmain(){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)fori :=0;i<15;i++{longArr[i]=i *10}processArray(longArr)func(){deferfunc(){ifr :=recover();r!=nil{fmt.Println("Caught panic from demonstratePanic:", r)}}()demonstratePanic()}()nestedPanic()// 所有 panic 都被 recover,程序继续正常运行 fmt.Println("\nProgram continues normally")}执行结果:
Result:5Recovered from panic: division by zero Result:0Recovered from array access panic Value at index10:100Before 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 兜底 |