Defer, Panic, 和 Recover – Go 语言博客

内容纲要

Go 语言拥有常见的控制流机制:if、for、switch、goto。它还有 go 语句来在一个独立的 goroutine 中运行代码。在这里,我想讨论一些不太常见的:defer、panic 和 recover。

defer 语句将一个函数调用推入一个列表。保存的调用列表在包围函数返回后执行。Defer 通常用于简化执行各种清理操作的函数。

例如,让我们看一个打开两个文件并将一个文件的内容复制到另一个文件的函数:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }

    written, err = io.Copy(dst, src)
    dst.Close()
    src.Close()
    return
}

这有效,但有一个错误。如果 os.Create 的调用失败,函数将返回而不关闭源文件。这可以通过在第二个 return 语句之前放置一个调用 src.Close 来轻松解决,但如果函数更复杂,问题可能不会被轻易注意和解决。通过引入 defer 语句,我们可以确保文件总是被关闭:

func CopyFile(dstName, srcName string) (written int64, err error) {
    src, err := os.Open(srcName)
    if err != nil {
        return
    }
    defer src.Close()

    dst, err := os.Create(dstName)
    if err != nil {
        return
    }
    defer dst.Close()

    return io.Copy(dst, src)
}

Defer 语句允许我们考虑在打开文件后立即关闭它,保证无论函数中的 return 语句有多少,文件都会被关闭。

defer 语句的行为是直接和可预测的。有三个简单的规则:

  1. 当 defer 语句被评估时,延迟函数的参数会被评估。
  2. 在包围函数返回后,按 Last In First Out 顺序执行延迟函数调用。
  3. 延迟函数可以读取并分配返回函数的命名返回值。

Panic是一个内置函数,它停止正常的控制流并开始恐慌。当函数 F 调用 panic 时,F 的执行停止,F 中的任何延迟函数正常执行,然后 F 返回给其调用者。对于调用者,F 的行为就像对 panic 的调用一样。该过程继续沿着堆栈向上,直到当前 goroutine 中的所有函数都返回,此时程序崩溃。

Recover是一个内置函数,它重新获得一个恐慌的 goroutine 的控制。Recover 只在延迟函数内部有用。在正常执行期间,对 recover 的调用将返回 nil 并且没有其他效果。如果当前的 goroutine 正在恐慌,对 recover 的调用将捕获给 panic 的值并恢复正常执行。

以下是一个演示 panic 和 defer 机制的示例程序:

package main

import "fmt"

func main() {
    f()
    fmt.Println("Returned normally from f.")
}

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    fmt.Println("Calling g.")
    g(0)
    fmt.Println("Returned normally from g.")
}

func g(i int) {
    if i > 3 {
        fmt.Println("Panicking!")
        panic(fmt.Sprintf("%v", i))
    }
    defer fmt.Println("Defer in g", i)
    fmt.Println("Printing in g", i)
    g(i + 1)
}

该程序的输出将是:

Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

总之,defer 语句(无论是否与 panic 和 recover 一起使用)为控制流提供了一个不寻常且强大的机制。它可以用来模拟其他编程语言中由特殊结构实现的许多功能。

文章由 Andrew Gerrand 编写

原文链接

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注