ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

3.7 Go panic 与 recover 学习笔记

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 + recoverrecover 只能在 defer 内调用,捕获 panic 值并恢复执行
recover 修改返回值利用命名返回值,在 defer 中设置 panic 情况的返回值
捕获运行时 panic数组越界、nil 指针等运行时错误也能被 recover 捕获
嵌套 panic/recoverpanic 只被最内层 recover 捕获,外层 recover 返回 nil
recover 后继续运行panic 被 recover 后,程序从恢复点继续正常执行
最佳实践可预期错误用error,不可恢复错误用 panic/recover 兜底
返回列表