# 要 `panic!` 还是不要 `panic!`

**To `panic!` or Not to `panic!`**


那么该怎样确定，什么时候应该调用 `panic!`，以及什么时候应该返回 `Result` 呢？在代码中止时，就没有办法恢复了。当然可以在任何错误情形下调用 `panic!`，而不管存不存在可能的恢复方式，不过这个时候就是代码编写者本人，代替代码在做出这种情形为不可恢复的确定了。而在选择了返回某个 `Result` 值是，就赋予了调用代码（the calling code）各种选项。调用代码就可以根据其自身情况，而选择尝试恢复，或者他可以决定在此情形下的某个 `Err` 是不可恢复的，进而他就可以调用 `panic!` 而将可恢复错误，转变为不可恢复错误。这样看来，在对某个可能失败的函数进行定义时，返回一个 `Result` 就是良好的默认选择。

而在示例程序、原型代码及测试等中，那么比起返回 `Result`，编写程序中止代码就要更合适。接下来就要探讨一下为何这样讲，随后就要讨论一些编译器无法搞清楚，但作为代码编写者的人类却明白程序失败不可能发生的情形。本章将以一些有关在库代码中，如何确定要不要中止程序的守则结束（in situations such as examples, prototype code, and tests, it's more approciate to write code that panics instead of returning a `Result`. Let's explore why, then discuss situations in which the compiler can't tell that failure is impossible, but you as a human can. The chapter will conclude with some general guidelines on how to decide whether to panic in library code）。


## 示例程序、原型代码与测试

**Examples, Prototype Code, and Tests**


在编写用于演示某些概念的示例程序时，若同时包含一些健壮的错误处理代码，就会令到示例程序不那么明晰。在示例程序中，到某个诸如 `unwrap` 这样的可能会中止程序运行方法的调用，确信就表明那是一个是要这个应用程序对错误进行处理的占位符，而根据接下来代码所做的事情，这样的调用会有所不同。

与此类似，`unwrap` 与 `expect` 方法在构造原型程序，尚未准备好确定如何处理错误时，是十分方便的。这些方法在代码中留下了清楚的一些记号，这些记号在已做好准备让程序更为健壮时会用到。

而当方法调用在测试中失败时，即使那个方法并非正在测试的某些功能，也会希望整个测试失败。由于 `panic!` 正是将测试标记为失败的方式，那么调用 `unwrap` 或 `expect`，则正是要用到的了。


## 相比于编译器，代码编写者掌握了更多信息的情形

**Cases in Which You Have More Information Than the Compiler**


在有着确保了 `Result` 将有着 `Ok` 值的其他某些逻辑，但编译器对此逻辑却一无所知时，调用 `unwrap` 或 `expect` 也是恰当的。这时将仍然有个需要处理的 `Result` 值：对于不论所调用的什么操作，即使在当前特定情形下，逻辑上失败绝无可能，但所调用的操作原本总体上仍是有失败可能。在经由亲自检查代码，而能确保绝不会有 `Err` 变种时，调用 `unwrap` 就是完美可接受的，且将自己设想的绝不会有 `Err` 变种的原因，在 `expect` 文本中撰写出来，这样做甚至更佳。下面就是一个示例：

```rust
fn main () {
    use std::net::IpAddr;

    let home: IpAddr = "127.0.0.1"
        .parse()
        .expect("硬编码的 IP 地址应是有效的");
}
```

这里是在通过解析硬编码字符串，创建一个 `IpAddr`。可以看到 `127.0.0.1` 是个有效的 IP 地址，因此这里使用 `expect` 是可接受的。然而，有着一个硬编码的、有效的字符串，并未改变 `parse` 方法返回值类型：这里仍将得到一个 `Result` 类型，同时由于编译器不是足够聪明到发现这个字符串总是个有效的 IP 地址，那么编译器仍将要求，以 `Err` 变种是一种可能性那样，对这个 `Result` 进行处理。在这个 IP 地址为来自用户输入，而非这里的硬编码到程序中，进而 *确实* 有着失败可能时，无疑就要打算对这个 `Result` 进行更为健壮的处理了。这种提及这个 IP 地址为硬编码的假设，将提醒到在将来，需要从其他来源获取这个 IP 地址时，就要把 `expect` 修改为更好的错误处理代码。


## 错误处理准则

**Guidelines for Error Handling**


在代码可能以糟糕状态结束运行时，那么让代码中止运行就是明智的。在这种情形下，所谓 *糟糕状态（a bad state）* 就是在某种假设、保证、合约，或恒值已被破坏，譬如在无效值、矛盾值，或缺失值被传递到所编写代码 -- 加上以下的一项或多项：

- 糟糕状态是某些不期望的东西，他们与偶发的东西相反，比如用户输入的错误格式数据；

- 在此处之后的代码，需要依赖于不处在这种糟糕状态，而不是在接下来的每一步都检查这个问题；

- 没有以自己所使用的类型，来编码该信息的好办法。在第 17 章的 [“将状态与行为编码为类型”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#将状态与行为当作类型编码) 小节，就会贯穿一个这里所意指的示例。


在有人调用到咱们的代码，并传入了无意义的值时，在可以的情况下，最好返回一个错误，这样库用户就可以确定在那样的情况下，他们打算做什么。然而在继续执行下去会不安全或有危害的情形中，那么最佳选择就会时调用 `panic!`，并警醒使用到咱们库的人他们代码中的错误，这样在他们开发过程中就可以修好那个代码错误。与此类似，在调用不在掌控中的外部代码，且该外部代码返回了无法修复的无效状态时，那么 `panic!` 通常就是恰当选择。

不过在失败为预期的时，那么相比于构造一个 `panic!` 调用，返回一个 `Result` 则更为恰当。这类示例包括给到解析器错误格式数据，或某个返回了表示已达到访问数限制的 HTTP 请求等。在这些情况下，返回一个 `Result` 就表示失败是一种调用代码必须确定如何处理的预期可能。

在所编写代码被使用无效值调用，而执行了某种可能将用户置于危险境地的操作时，那么代码就应首先对这些值进行检查，并在这些值无效时中止运行。这主要是处于安全原因：尝试运行于无效数据，就会将代码暴露于漏洞。这就是在尝试超出边界的内存访问时，标准库会调用 `panic!` 的主要原因：尝试访问不属于当前数据结构的内存，是个常见的安全问题。函数通常有着 *合约（contracts）*：只在输入满足特定要求时，他们的行为才有保证。那么由于合约破坏总是表明调用者侧的代码错误，且这种错误并非是要调用代码必须显式处理的那种错误，因此在合约被破坏时的中止运行就说得通了。实际上，调用代码是没有恢复的合理方法的；调用的 *代码编写者* 需要修复该代码。应在函数的 API 文档中，解释函数的合约，尤其是在合约破坏会导致中止运行时。

但是，在全部的函数中，进行大量错误检查，则会显得冗长而烦人。幸运的是，可使用 Rust 的类型系统（并因此由编译器完成类型检查），来完成许多的检查。在函数有着作为参数的特定类型时，就可以在知悉编译器已经确保有着有效值的情况下，着手处理代码的业务逻辑。比如，在有着一个不同于 `Option` 的类型时，程序就期望有 *某个东西（something）* 而非 *什么也没有（nothing）*。代码这时就不必处理 `Some` 与 `None` 变种的两种情形：无疑将只有一种有着某个值的情形。尝试将无值传递给该函数的代码，甚至都不会编译，那么该函数就不必在运行时对那样的情况进行检查了。另一个示例则是使用某个诸如 `u32` 无符号整数，这就确保了参数绝不会是个负数。


## 创建用于验证的定制类型

**Creating Custom Types for Validation**


接下来将这个运用 Rust 的类型系统，来确保有着有效值的概念，进行进一步拓展，而看看创建一个用于验证的定制类型。回顾在第二章中的猜数游戏，其中的代码要求用户猜出一个 `1` 与 `100` 之间的数字。在将用户猜的数字与那里的秘密数字比对之前，是绝无对用户猜数是否处于 `1` 与 `100` 之间，进行过验证的；那里只验证过猜数为正数。在这个示例中，后果并不是非常可怕：这里的输出 “太大了” 或 “太小了” 仍将正确。但引导用户朝向有效的猜数，并在用户猜出不在该范围的数，与用户敲入了比如一些字母时，而有不同的表现，将是一项有用的功能增强。

完成此功能增强的一种方式，将是将猜数解析为一个 `i32` 而非仅仅为一个 `u32`，从而允许潜在的负数，并在随后键入一个该数位于范围中的检查，像下面这样：

```rust
        loop {
            // --跳过--

            let guess: i32 = match guess.trim().parse() {
               Ok(num) => num,
               Err(_) => { println! ("请输入一个数字！"); continue },
            };

            if guess < 1 || guess > 100 {
                println! ("秘密数字将在 1 和 100 之间");
                continue;
            }

            match guess.cmp(&secret_number) {
                // --跳过--
        }
```

其中的 `if` 表达式，对这里的值是否超出范围进行了检查，告诉用户这个问题，并调用 `continue` 来开始下一次循环迭代而请求另一个猜数。在这个 `if` 表达式之后，就可继续进行 `guess` 与秘密数字之间的比较，获悉 `guess` 是在 `1` 与 `100` 之间。

然而这并非一种理想的方案：若程序只运行在 `1` 与 `100` 之间的值这一点至关重要，且程序有着许多有此要求的函数，而在每个函数中都进行这样的一个检查，就会显得冗长乏味（并可能影响性能）。

相反，这里可以构造一种新类型，并将那些验证放入某个函数，从而创建出该类型的一个示例，而非在各个地方重复这些验证。那样的话，这些函数就可以在他们的签名中，安全地使用这种新类型，并信心十足地使用他们接收到的那些值了。下面清单 9-13 给出了一种定义 `Guess` 类型的方式，在 `new` 函数接收到一个 `1` 与 `100` 之间的值时，这种方式下将只创建一个 `Guess` 的实例。

```rust
pub struct Guess {
    value: i32,
}

impl Guess {
    pub fn new(value: i32) -> Guess {
        if value < 1 || value > 100 {
            panic! ("Guess 类型值必须在 1 与 100 之间，收到的是 {}", value);
        }

        Guess { value }
    }

    pub fn value(&self) -> i32 {
        self.value
    }
}
```

*清单 9-13：只会在值处于 `1` 与 `100` 之间，才继续执行的一个 `Guess` 类型*


首先，这里定义了一个名为 `Guess`，带有一个叫做 `value`、保存了一个 `i32` 值字段的结构体。这就是要存储数字的地方。

随后这里在 `Guess` 上实现了一个名为 `new` 的关联函数，其创建出一个 `Guess` 类型的实例。这个 `new` 函数被定义为有着一个名为 `value`、类型为 `i32` 的参数，以及要返回一个 `Guess` 类型值。`new` 函数体中的代码，对 `value` 进行了测试，从而确保 `value` 是在 `1` 与 `100` 之间。若 `value` 未通过此测试，那么就做出一个 `panic!` 调用，由于创建一个超出此范围的 `Guess` 会破坏 `Guess::new` 所依赖的合约，因此这就会警醒到编写调用代码的程序员，他们有个需要修复的代码错误。`Guess::new` 可能中止运行的条件，应在其公开的 API 文档中，进行说明。在第 14 章就会涉及到在所创建的文档中，表示 `panic!` 可能性的一些约定。在 `value` 通过该测试时，这里就会创建一个将 `value` 字段设置为那个 `value` 参数的新 `Guess` 类型值，并返回这个 `Guess` 类型值。

接下来，这里实现了一个名为 `value`、借用了 `self`，不带任何其他参数，并返回一个 `i32` 的方法。由于这类方法的目的，是要从一些字段获取数据并加以返回，因此有时就被叫做 *取值方法（getter）*。因为 `Guess` 结构体的这个 `value` 字段是私有的，那么这个公开方法就是必要的。这个 `value` 字段作为私有至关重要，这样使用这个 `Guess` 结构体的代码就不被允许直接设置 `value`：该模组外部的代码，*必须* 使用 `Guess::new` 函数，来创建 `Guess` 的实例，这样就确保了 `Guess` 不会有未经 `Guess::new` 函数中条件检查的 `value`。

现在某个有着一个 `1` 与 `100` 之间参数，或只返回 `1` 与 `100` 之间数字的函数，就可以在其函数签名中，声明他所取参数或其返回值为 `Guess` 类型而非 `i32` 类型，而不再需要在其函数体中完成任何额外检查了。


# 本章小结

Rust 的那些错误处理特性，被设计用于帮助编写更为健壮的代码。`panic!` 这个宏，发出了程序处于其无法处理状态的信号，并让咱们告知进程停下来，而不是尝试以无效或不正确的一些值继续运行。而 `Result` 这个枚举则使用了 Rust 的类型系统，来表示以代码可以从中恢复过来的某种方式的一些操作失败（the `Result` enum uses Rust's type system to indicate that operations might fail in a way that your code could recover from）。还可使用 `Result` 来告诉调用了咱们代码的代码，需要处理潜在的成功与失败情形。在一些适当情形下，运用 `panic!` 与 `Result` 就会令到咱们的代码在各种不可避免的问题面前，更加可靠。

既然这里已经见识到标准库在 `Option` 与 `Result` 枚举上，运用到泛型的一些有用方式，那么接下来就要谈及泛型的原理，以及怎样在咱们的代码中运用泛型。
