3.7 Go panic 与 recover 学习笔记

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 兜底
相关推荐
JavaPub-rodert1 小时前
我把 OpenAI 协议塞进了 Go 工具库:go-commons 开始支持 AI 了
开发语言·人工智能·golang
圣殿骑士-Khtangc1 天前
Go错误处理最佳实践进阶从error到panic的完整指南
golang
运维开发笔记1 天前
3.6 Go defer 语句学习笔记
golang
FfHUCisI1 天前
Golang HTTP 路由设计与请求处理
开发语言·http·golang
存在morning1 天前
【Python 开发实践 一】Python vs Go vs Java 三门语言对比
java·python·golang
圣殿骑士-Khtangc2 天前
Go字符串高效拼接性能对比与底层原理分析
服务器·前端·golang
xzlAwin2 天前
Win10安装Go语言多版本管理器g工具
开发语言·golang
FfHUCisI2 天前
Golang RESTful API 设计原则
开发语言·golang·restful
大可-2 天前
Go Air 热重载安装配置指南
开发语言·后端·golang