引入

程序有时会碰到完全无法处理的情况——数组越界、空指针、除零。Go 里这类严重问题会触发 panic,程序会立刻停止正常运行。但 Go 也提供了 recover 机制,让你在 defer 里把程序从崩溃边缘拉回来。

正文

定义

定义

panic 是 Go 的运行时错误机制,调用 panic() 后程序立即停止正常执行,开始执行所有 defer 语句,然后崩溃。recover() 只能在 defer 函数内调用,用于捕获 panic 并恢复程序的正常执行。

语法

// 触发 panic
panic("出错了")
 
// 在 defer 中恢复
defer func() {
    if r := recover(); r != nil {
        fmt.Println("恢复了:", r)
    }
}()

例子

触发 panic

func main() {
    fmt.Println("开始")
    panic("严重错误")
    fmt.Println("不会执行")
}
// 输出:
// 开始
// panic: 严重错误

panic() 后面的代码不会再执行,程序打印 panic 信息和调用栈后退出。

panic 时 defer 仍然执行

func main() {
    defer fmt.Println("defer 执行了")
    panic("崩溃了")
}
// 输出:
// defer 执行了
// panic: 崩溃了

即使 panic 了,之前声明的 defer 照样会执行,按 LIFO 顺序逐个运行。

用 recover 恢复

func safeDivide(a, b int) (result int) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("捕获 panic:", r)
            result = 0
        }
    }()
    return a / b
}
 
func main() {
    fmt.Println(safeDivide(10, 2)) // 5
    fmt.Println(safeDivide(10, 0)) // 捕获 panic: runtime error: ...
                                   // 0
}

recover()defer 里调用,如果当前函数正在 panic,就能拿到 panic 的值并恢复正常执行。

recover 必须在 defer 里

func main() {
    // 错误写法:直接调用 recover 无效
    r := recover()
    fmt.Println(r) // nil
 
    panic("test")
}

recover() 只有在 defer 函数中调用才有效,直接调用永远返回 nil

recover 只恢复当前 goroutine

func main() {
    defer func() {
        recover() // 无法恢复子 goroutine 的 panic
    }()
 
    go func() {
        panic("子 goroutine 崩溃")
    }()
 
    time.Sleep(time.Second)
}

recover() 只能恢复当前 goroutine 的 panic,其他 goroutine 的 panic 无法跨 goroutine 捕获。

常见 panic 场景

// 数组越界
arr := [3]int{1, 2, 3}
_ = arr[5] // panic: index out of range
 
// 空指针
var p *int
_ = *p // panic: nil pointer dereference
 
// 向 nil map 写入
var m map[string]int
m["key"] = 1 // panic: assignment to entry in nil map
 
// 重复关闭 channel
ch := make(chan int)
close(ch)
close(ch) // panic: close of closed channel

这些是 Go 运行时自动触发的 panic,不需要手动调用 panic()

判断是否 panic

func handle() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Printf("panic 类型: %T, 值: %v\n", r, r)
        } else {
            fmt.Println("正常结束")
        }
    }()
    // 正常或 panic 的代码
}

recover() 返回 interface{} 类型,可以用 %T 查看 panic 值的具体类型。

常见写法

写法说明
panic("msg")手动触发 panic
recover()在 defer 中捕获 panic
if r := recover(); r != nil { }判断并处理 panic
defer func() { recover() }()简洁的 panic 恢复
panic(errors.New("err"))panic 传 error 值

特点

  • panic 会让程序立即停止正常执行,所有 defer 执行完后崩溃
  • recover() 只能在 defer生效,直接调用无效
  • recover() 只能恢复当前 goroutine 的 panic
  • Go 不鼓励用 panic/recover 替代正常错误处理,日常用 error
  • panic 适合真正无法恢复的情况,比如初始化失败、程序逻辑错误
  • 运行时错误(越界、空指针等)会自动触发 panic
  • panic 的值是 interface{} 类型,可以是任意值

理解

panic 是 Go 的”紧急按钮”,程序遇到无法处理的问题时拉响警报。recover 则是安全网,在 defer 里把程序从崩溃中救回来。但 Go 的设计哲学是用 error 处理可预见的错误,panic 只留给真正的意外情况。别把 panic/recover 当 try-catch 用。

引出

panicrecover 是 Go 控制流的最后一环。接下来进入数据类型的学习,从最基础的 Go 数组 开始,看看 Go 里怎么存储一组固定长度的数据。