From 3226e226272ec7f847bae308052bee93448485cc Mon Sep 17 00:00:00 2001 From: Hector PENG Date: Fri, 20 Mar 2026 15:22:10 +0800 Subject: [PATCH] Updated src/error_handling/panic_or_not.md'. --- projects/expect_demo/Cargo.toml | 6 ++ projects/expect_demo/src/main.rs | 9 ++ projects/guessing_game/src/guessing_game.rs | 17 ++++ projects/guessing_game/src/lib.rs | 1 + src/error_handling/panic_or_not.md | 95 ++++++++------------- src/oop/implementing.md | 10 +-- 6 files changed, 71 insertions(+), 67 deletions(-) create mode 100644 projects/expect_demo/Cargo.toml create mode 100644 projects/expect_demo/src/main.rs create mode 100644 projects/guessing_game/src/guessing_game.rs create mode 100644 projects/guessing_game/src/lib.rs diff --git a/projects/expect_demo/Cargo.toml b/projects/expect_demo/Cargo.toml new file mode 100644 index 0000000..72947aa --- /dev/null +++ b/projects/expect_demo/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "expect_demo" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/projects/expect_demo/src/main.rs b/projects/expect_demo/src/main.rs new file mode 100644 index 0000000..a4739ca --- /dev/null +++ b/projects/expect_demo/src/main.rs @@ -0,0 +1,9 @@ +fn main() { + use std::net::IpAddr; + + let home: IpAddr = "327.0.0.1" + .parse() + .expect("硬编码的 IP 地址应是有效的"); + + println! ("{home}"); +} diff --git a/projects/guessing_game/src/guessing_game.rs b/projects/guessing_game/src/guessing_game.rs new file mode 100644 index 0000000..cd54aa1 --- /dev/null +++ b/projects/guessing_game/src/guessing_game.rs @@ -0,0 +1,17 @@ +pub struct Guess { + value: i32, +} + +impl Guess { + pub fn new(value: i32) -> Guess { + if value < 1 || value > 100 { + panic!("猜数值必须在 1 和 100 之间,得到了 {value}。"); + } + + Guess { value } + } + + pub fn value(&self) -> i32 { + self.value + } +} diff --git a/projects/guessing_game/src/lib.rs b/projects/guessing_game/src/lib.rs new file mode 100644 index 0000000..5a99b1b --- /dev/null +++ b/projects/guessing_game/src/lib.rs @@ -0,0 +1 @@ +mod guessing_game; diff --git a/src/error_handling/panic_or_not.md b/src/error_handling/panic_or_not.md index 59e4147..449e90c 100644 --- a/src/error_handling/panic_or_not.md +++ b/src/error_handling/panic_or_not.md @@ -1,87 +1,68 @@ # 要 `panic!` 还是不要 `panic!` -**To `panic!` or Not to `panic!`** - -那么,当代码死机无法恢复时,咱们要如何决定何时应该调用 `panic!`,何时应返回 `Result` 呢?对于任何错误情形,无论是否有可能恢复,咱们都可以调用 `panic!`,但这样一来,咱们就代表调用代码,做出了无法恢复的决定。在咱们选择返回一个 `Result` 值时,咱们就给了调用代码一些选择。调用代码可以选择以适合其情况的方式尝试恢复,或者他可以决定在这种情况下的某个 `Err` 值是不可恢复的,因此他可以调用 `panic!` 并将咱们的可恢复错误,变成一个不可恢复的错误。因此,在咱们定义某个可能失败的函数时,返回 `Result` 是一个不错的默认选择。 - -在示例程序、原型代码及测试等情况下,编写会死机而非返回一个 `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. +那么,咱们要怎样决定何时咱们应该调用 `panic!`,何时应返回 `Result` 呢?代码停止运行后,就没有办法恢复。咱们可以对任何错误情形都调用 `panic!`,无论是否有可能的方法恢复,但这样一来咱们就代表调用代码做出了无法恢复的决定。当咱们选择返回 `Result` 值时,咱们给予了调用代码选择。调用代码可以选择以适合其情况的方式尝试恢复,或者他可以决定这种情况下 `Err` 值是不可恢复的,因此他可以调用 `panic!` 并转换咱们的可恢复错误为不可恢复的错误。因此,当咱们在定义可能失败的函数时,返回 `Result` 是个不错的默认选择。 +在示例、原型及测试等情况下,编写会终止运行的代码比返回 `Result` 的代码更合适。我们来探讨一下原因,然后讨论在哪些情况下,编译器无法区分失败是不可能的,但作为人类咱们却可以。本章将以一些关于在库代码中怎样决定是否要终止运行的一般准则完结。 ## 示例程序、原型代码与测试 -**Examples, Prototype Code, and Tests** +当咱们编写示例演示某一概念时,同时包含健壮的错误处理代码会使示例变得不那么明了。在示例中,可以理解的是,到像是 `unwrap` 这样会终止运行的方法调用,是指咱们希望应用程序来处理错误的一种占位符方式,调用哪个方法可能会根据咱们的代码其余部分正在执行的操作而有所不同。 -在咱们编写说明某些概念的示例程序时,同时包含一些健壮的错误处理代码,会使示例程序变得不那么明了。在示例程序中,到像 `unwrap` 这样可能会死机的某个方法的调用,往往是作为咱们打算咱们应用程序处理错误方式的占位符,而根据咱们代码其余部分的操作,咱们程序处理错误方式可能不一样。 +同样,在咱们正在构建原型而尚未准备好决定如何处理错误时,`unwrap` 和 `expect` 两个方法非常方便。他们会在咱们的代码中留下清晰的标记,以便咱们准备好让程序更健壮时使用。 -同样,在咱们原型设计时,准备好决定如何处理错误前,`unwrap` 和 `expect` 两个方法非常方便。他们会在咱们的代码中,留下一些清晰的标记,以便咱们准备好让程序更健壮时使用。 - -如果一次测试中的某个方法调用失败,咱们会希望整个测试都失败,即使该方法并不属于被测试的功能。因为 `panic!` 是标记测试失败的方式,调用 `unwrap` 或 `expect` 正是不二之选。 +当方法调用在测试中失败时,咱们会希望整个测试都失败,即使该方法不属于被测试的功能。因为 `panic!` 是测试被标记为失败的方式,调用 `unwrap` 或 `expect` 正是不二之选。 +## 咱们比编译器掌握更多信息时 -## 相比于编译器,咱们掌握了更多信息的情况 - -**Cases in Which You Have More Information Than the Compiler** - -当咱们有其他确保 `Result` 值将有着一个 `OK` 值的逻辑时,调用 `unwrap` 或 `expect` 也是合适的,但编译器并不理解这些逻辑。咱们仍将有个咱们需要处理的 `Result` 值:无论咱们调用的是何种操作,在一般情况下都有可能失败,即使在咱们特定情况下该操作逻辑上是不可行的。若咱们能通过手动检查代码,确保咱们绝不会有个 `Err` 变种,那么调用 `unwrap` 就是完全可接受的,甚至最好在 `expect `文本中,记录下咱们认为不会出现 `Err` 变种的原因。下面是个例子: +当咱们有某些别的逻辑可以确保 `Result` 值将有着 `OK` 值,而该逻辑并非编译器所掌握时,调用 `expect` 也是合适的。咱们仍将有个咱们需要处理的 `Result` 值:无论咱们调用何种操作,通常仍然有失败的可能性,即使在咱们特定情况下逻辑上失败是不可能的。当咱们可以通过手动检查代码来确保咱们将绝不会有 `Err` 变种时,那么调用 `expect` 并在文本参数中记录咱们认为不会咱们将绝不会有 `Err` 变种的原因,是完全可以接受的。下面是个示例: ```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` 值,同时编译器仍将让我们按照 `Err` 变种为一种可能性一样,处理这个 `Result`,因为编译器还不够聪明,无法发现这个字符串始终是个有效的 IP 地址。在这个 IP 地址字符串是来自用户,而不是硬编码到程序中的时,就因此 *确实* 存在失败的可能性,那么我们肯定就会希望以一种更健壮的方式,处理这个 `Result`。在将来我们需要从其他来源获取该 IP 地址时,那么提及这个 IP 地址是硬编码的假设,将提醒我们将 `expect` 修改成更好的错误处理代码。 +我们通过解析硬编码字符串来创建一个 `IpAddr` 的实例。我们可以看到 `127.0.0.1` 是个有效的 IP 地址,因此在这里使用 `expect` 是可接受的。然而,有个硬编码的有效字符串并不会改变 `parse` 方法的返回类型:我们仍会得到 `Result` 值,同时编译器仍将让我们处理这个 `Result`,就像 `Err` 变种是一种可能性那样,因为编译器不够聪明,无法看出这个字符串始终是个有效的 IP 地址。当 IP 地址字符串来自用户而不是硬编码到程序中,而因此 *确实* 有着失败的可能性时,我们就肯定会希望以一种更健壮的方式处理 `Result`。在今后我们需要从某一别的来源获取 IP 地址时,那么提及这个 IP 地址是硬编码的假设,将提醒我们修改 `expect` 为更好的错误处理代码。 -## 错误处理指南 +## 错误处理指南/准则 -**Guidelines for Error Handling** +当咱们的代码可能最终处于糟糕的状态时,建议让咱们的代码终止运行。在这一语境下,所谓 *糟糕状态,bad state*,是指某种假设、保证、合约,或不变量已被破坏,比如当无效值、矛盾值或缺失值被传递给咱们的代码时 -- 加上以下一种或多种情况: -当咱们的代码有可能陷入某种糟糕的状态时,那么最好让咱们的代码死机。在此语境下,所谓 *糟糕状态,bad state* 是指某个假定、保证、合约,或恒定值被破坏,例如在无效值、矛盾值或缺失值被传递给咱们的代码时 -- 再加上以下一种或多种情况: +- 糟糕状态是指意外的情况,与偶尔发生的情况相反,比如用户输入错误格式的数据; +- 此后咱们的代码需要依赖于不处于这种不良状态,而不是在每一步都检查问题; +- 没有好的方法来以咱们使用的类型编码这些信息。我们将在第 18 章中的 [“将状态与行为编码为类型”](../oop/implementing.md#将状态与行为编码为类型) 中,举例说明这点。 -- 糟糕状态是指意想不到的某种情况,与偶尔会发生的情况相反,比如错误格式的用户输入数据等; +当有人调用咱们的代码并传入不合理的值时,最好在可以时返回一个错误,以便库的用户就可以决定在这种情况下他们打算做什么。但是,在继续执行可能不安全或有害的情形下,最好的选择可能是调用 `panic!` 并提醒使用咱们库的人注意他们代码中的 bug,以便他们在开发过程中修复它。同样,当咱们调用不受咱们控制的外部代码,并返回了咱们无法修复的无效状态时,`panic!` 通常是合适的。 -- 咱们此处之后的代码,需要依赖于不再处于这种不良状态,而不是在每一步都检查问题; +不过,当失败属于预期的时,返回 `Result` 就比调用 `panic!` 更合适。示例包括解析器被给予了格式错误的数据,或者返回表明已达速率限制的状态的 HTTP 请求等。在这些情况下,返回 `Result` 表明失败属于一种预期的可能性,调用代码必须决定如何处理。 -- 并无以咱们所使用类型,编码这些信息的某种良好方式。我们将在第 18 章 [“将状态和行为编码为类型”](../oop/implementing.md#将状态与行为编码为类型) 中,举例说明这个意思。 +当咱们的代码执行某项在使用无效值调用时,会置用户于风险中的操作时,咱们的代码应首先检查值是否有效的,并在值无效时终止运行。这主要是出于安全原因:尝试运行于无效数据上会暴露咱们的代码于漏洞之下。这是标准库在咱们尝试越界内存访问时调用 `panic!` 的主要原因:试图访问不属于当前数据结构的内存属于常见的安全问题。函数通常有 *合约,contracts*:他们的行为只有在输入满足特定要求时才有保证。在合约违反时终止运行是有道理的,因为违反合约总是表明某种调用方的 bug,而这不是咱们希望调用代码必须显式处理的错误类别。事实上,调用代码没有合理的恢复方法;调用 *程序员* 需要修复代码。函数的合约,尤其是违反合约将导致停止运行时,应在函数的 API 文档中予以说明。 -如果有人调用了咱们的代码并传入不合理的值,那么最好是咱们尽可能返回某个错误,这样该库的用户就可以决定,他们在这种情况下要做什么。但是,如果继续下去可能不安全或有害,那么最好的选择就可能是调用 `panic!`,并提醒使用咱们库的人他们代码中的错误,以便他们在开发过程中修复错误。类似地,若咱们调用的外部代码不在咱们的掌控中,而他返回了咱们无法修复的某种无效状态,这时通常也适合调用 `panic!`。 - -不过,当失败属于预期中的时,返回一个 `Result` 就比调用一次 `panic!` 更合适。例如,某个解析器得到畸形数据,或者某次 HTTP 请求返回了一个表明咱们已达速率限制的状态时。在这些情况下,返回一个 `Result` 就表明失败是预期的、调用代码必须决定如何处理的一种可能。 - -当咱们的代码执行某项若其调用的是无效值时,可能会给用户带来风险的操作时,咱们的代码就应首先检查这些值是有效的,并在值无效时死机。这主要是出于安全考虑:尝试在无效数据上操作,会使咱们的代码暴露于漏洞之中。这也是标准库会在咱们尝试越界内存访问时,调用 `panic!` 的主要原因:尝试访问不属于当前数据结构的内存,是个常见的安全问题。函数通常都有 *合约,contracts*:只有在输入满足特定要求时,他们的行为才有保证。在这种合约破坏时死机是有道理的,因为合约破坏总是表明某个调用方的错误,而这并不是咱们希望调用代码必须要显式处理的错误类型。事实上,并没有调用代码恢复的合理方式;调用的 *程序员* 需要修复代码。函数的合约,尤其是在破坏会导致死机时,应在该函数的 API 文档中加以说明。 +然而,在所有函数中进行大量错误检查将是冗长且烦人的。幸运的是,咱们可以使用 Rust 的类型系统(以及因此而由编译器完成的类型检查)为咱们完成许多的这些检查。当咱们的函数以特定类型作为参数时,咱们便可在明白编译器已确保咱们会有个有效值下,放心继续咱们代码的逻辑。例如,当咱们有着某一类型而不是 `Option` 时,咱们的程序会期望有 *某个东西,something* 而不是 *无值,nothing*。这样,咱们的代码就不必处理 `Some` 与 `None` 的两种情况:他将只有肯定有个值的一种情况。试图传递无值到咱们函数的代码甚至将不编译,因此咱们的函数不必在运行时检查这种情况。另一示例是使用诸如 `u32` 的无符号整数类型,这确保了参数绝不会是负数。 -然而,在所有咱们的函数中,进行大量的错误检查会显得冗长而烦人。幸运的是,咱们可使用 Rust 的类型系统(及因此由编译器完成的类型检查),为咱们完成许多的检查。若咱们的函数有某个特定类型的参数,咱们就可以在知悉编译器已确保了咱们有个有效值下,处理咱们代码的逻辑。例如,在咱们有个类型而不是 `Option` 时,那么咱们的程序就会期望得到 *某个东西,something*,而不是 *全无,nothing*。这样,咱们的代码就不必处理 `Some` 与 `None` 变种的两种情况:他只有肯定有某个值的一种情况。试图向咱们的函数传递全无的代码,甚至不会编译,因此咱们函数在运行时就不必检查这种情况。另一个例子是使用如 `u32` 的无符号整数类型,这确保参数绝不会是负数。 +## 用于验证的定制类型 +我们来把使用 Rust 的类型系统来确保我们有个有效值的想法再推进一步,探讨创建用于验证的定制类型。回顾第 2 章中的猜数游戏,其中我们的代码请求用户猜测 1 到 100 之间的数字。在将其与我们的秘密数字比对前,我们从未验证其是否介于这两个数字之间;我们只验证了猜数是个正数。在这种情况下,后果不是非常可怕:我们的 “太大” 或 “太小” 输出将仍然是正确的。但是,往有效猜数引导用户,并在用户猜的数字超出范围时,与用户输入字母时有不同的行为,将是个非常有用的功能增强。 -## 创建用于验证的定制类型 - -**Creating Custom Types for Validation** - -我们来把使用 Rust 的类型系统,确保我们有个有效值的想法向前推进一步,看看如何创建出一个用于验证的定制类型。请回顾第 2 章中的猜数游戏,其中我们的代码要求用户猜一个 `1` 到 `100` 之间的数字。在将用户的猜数与我们的秘密数字核对前,我们从未验证过其是否介于这两个数字之间;我们只验证了用户猜数是个正数。在这种情况下,后果并不严重:我们“太大”或“太小”的输出,将仍然是正确的。但是,往有效猜数引导用户,并在用户猜的数字超出范围时,及用户输入字母等时有不同的行为,这将是个非常有用的增强功能。 - -实现这一增强的一种方法,是将猜测值解析为 `i32`,而不是 `u32`,允许出现潜在的负数,然后添加一个数字在范围内的检查,就像这样: +实现这一点的一种方式将是解析猜测值为 `i32` 而不是仅为 `u32` 以允许潜在的负数,然后添加对数字在该范围内的检查,就像这样: 文件名:`src/main.rs` ```rust loop { // --跳过-- + let guess: i32 = match guess.trim().parse() { Ok(num) => num, - Err(_) => { - println! ("请输入一个数字!"); - continue - }, + Err(_) => continue, }; if guess < 1 || guess > 100 { @@ -95,13 +76,14 @@ fn main () { ``` -其中 `if` 表达式会检查我们的值是否超出范围,告诉用户问题所在,并调用 `continue` 开始循环的下一次迭代并请求另一个猜数。在这个 `if` 表达式后,我们可以在清楚 `guess` 介于 `1` 和 `100` 之间下,继续 `guess` 与秘密数字间的比较。 +其中 `if` 表达式检查我们的值是否超出范围,告诉用户问题所在,并调用 `continue` 开始循环的下一次迭代并请求另一个猜数。在这个 `if` 表达式后,我们可以在知道 `guess` 介于 1 和 100 之间下,继续 `guess` 与秘密数字间的比较。 -然而,这不是一种理想解决方案:在程序只对 `1` 到 `100` 间的值进行操作绝对重要,并且程序有着许多有此要求的函数,那么在每个函数中都进行这样的检查,将是非常乏味的(而且可能会影响性能)。 +然而,这不是一种理想解决方案:若程序只运行于 1 到 100 之间的值这点绝对重要,而且他有许多有着这一要求的函数时,在每个函数中都进行这样的检查将是乏味的(并且可能会影响性能)。 -相反,我们可在一个专门模组中构造一个新类型,并把这些验证放入一个函数中,从而创建出该类型的实例,而不是在各处重复这些验证。这样,函数就可以安全地在其签名中使用这个新类型,并放心地使用接收到的值。下面清单 9-13 展示了定义 `Guess` 类型的一种方法,只有在 `new` 函数接收到介于 `1` 和 `100` 之间的值时,才会创建出一个 `Guess` 的实例。 +相反,我们可在专用模组中构造一个新类型,而放置验证于创建该类新的实例的函数中,而不是在各处重复这些验证。这样,函数就可以安全地在其签名中使用新的类型并放心地使用他们收到的值。下面清单 9-13 展示了定义 `Guess` 类型的一种方式,将只在 `new` 函数收到 1 和 100 之间的值时才创建 `Guess` 的实例。 + 文件名:`src/guessing_game.rs` ```rust @@ -112,7 +94,7 @@ pub struct Guess { impl Guess { pub fn new(value: i32) -> Guess { if value < 1 || value > 100 { - panic!("猜数值必须在 1 与 100 之间,得到了 {value}。"); + panic!("猜数值必须在 1 和 100 之间,得到了 {value}。"); } Guess { value } @@ -124,30 +106,27 @@ impl Guess { } ``` -*清单 9-13:只有在处于 `1` 与 `100` 之间的值下才会继续的 `Guess` 类型* +**清单 9-13**:在 1 和 100 之间的值下才将继续的 `Guess` 类型 -首先,我们创建了个名为 `guessing_game` 的新模组。接着,我们在该模块中定义了个名为 `Guess` 的结构体,该结构体有个名为 `value` 的字段,其中存放着一个 `i32`。数字将存储于该处。 +请注意,`src/guessing_game.rs` 中的这段代码依赖于在 `src/lib.rs` 中添加模组声明 `mod guessing_game;`,我们未在这里展示。在这个新模组的文件中,我们定义了个名为 `Guess` 的结构体,有个名为 `value` 保存一个 `i32` 值的字段。这是猜数将保存之处。 -然后,我们在 `Guess` 上实现了一个名为 `new` 的关联函数,创建 `Guess` 值的实例。`new` 函数被定义为有个名为 `value`,类型为 `i32` 的参数,并返回一个 `Guess`。`new` 函数主体中的代码,会测试 `value` 确保他介于 `1` 和 `100` 之间。在 `value` 没有通过此测试时,我们进行一次 `panic!` 调用,这将提醒编写调用代码的程序员,他们有一个需要修复的错误,因为以在此范围之外的一个 `value` 创建一个 `Guess`,将违反 `Guess::new` 所依赖的合约。`Guess::new` 可能会死机的那些条件,应该在面向公众的 API 文档中被提及;我们将在第 14 章中,介绍在咱们创建的 API 文档中,说明 `panic!` 可能性的文档约定。在 `value` 确实通过了测试时,我们就会创建出一个新的 `Guess`,并将其 `value` 字段设置为参数 `value`,然后返回该 `Guess`。 +然后,我们在 `Guess` 上实现一个名为 `new` 的关联函数,创建 `Guess` 值的实例。`new` 函数被定义为有一个名为 `value` 类型 `i32` 的参数,并返回 `Guess`。`new` 函数主体中的代码会测试 `value`,以确保其在 1 和 100 之间。当 `value` 未通过这一测试时,我们调用 `panic!`,这将提醒正在编写调用代码的程序员,他们有个需要修复的 bug,因为以超出这个范围的 `value` 创建 `Guess` 将违反 `Guess::new` 所依赖的合约。`Guess::new` 可能终止运行的情况应在其面向公众的 API 文档中讨论;我们将在第 14 章中,咱们创建的 API 文档里,介绍表明 `panic!` 可能性的文档约定。当 `value` 确实通过了测试时,我们会创建一个新的 `Guess`,将其 `value` 字段设置为 `value` 参数并返回 `Guess` 值。 +接下来,我们实现一个名为 `value` 的方法,其会借用 `self`,没有任何其他参数,并返回 `i32`。这种方法有时称为 *getter*,因为他的目的是获取其字段中的某一数据并返回他。这个公开方法是必要的,因为 `Guess` 结构体的 `value` 字段为私有。`value` 字段为私有非常重要,这样使用 `Guess` 结构体的代码不被允许直接设置 `value`:`guessing_game` 模组外的代码 *必须* 使用 `Guess::new` 函数来创建 `Guess` 实例,从而确保 `Guess` 无法拥有未经 `Guess::new` 函数中的条件检查的 `value`。 -接下来,我们实现了一个名为 `value` 的方法,该方法借用了 `self`,不带任何其他参数,并返回一个 `i32`。这种类别的方法,有时被称为 *getter*,因为他的目的是获取其字段中的某个数据并返回。这个公共方法是必要的,因为 `Guess` 结构体的 `value` 字段是私有的。`value` 这个字段是私有的很重要,这样使用 `Guess` 这个结构体的代码,就不能直接设置 `value`:`guessing_game` 模组之外的代码,*必须* 使用 `Guess::new` 函数创建 `Guess` 的实例,从而确保某个 `Guess` 不可能有着一个,未经 `Guess::new` 函数中条件检查过的 `value`。 - -某个有着参数或返回值仅为 `1` 到 `100` 之间数字的函数,随后就可以在其签名中,声明他接收或返回的是 `Guess` 而不是 `i32`,并且不需要在其主体中进行任何额外的检查。 - +有着仅为 1 与 100 之间数字的参数,或仅返回 1 和 100 之间数字的函数,现在就可以在其签名中声明他接收或返回 `Guess` 而不是 `i32`,并且将无需在其主体中进行任何额外的检查。 # 本章小结 -Rust 的这些错误处理特性,旨在帮助咱们编写更健壮的代码。 +Rust 的错误处理特性,旨在帮助咱们编写更健壮的代码。 -- `panic!` 这个宏表示咱们的程序处于其无法处理的某种状态,让咱们可以告诉进程停止,而不是尝试继续处理无效或不正确的值; -- `Result` 枚举使用 Rust 的类型系统,表示可能会失败,但咱们代码可从中恢复的那些操作。咱们可以使用 `Result` 告诉调用咱们代码的代码,他也需要处理潜在的成功或失败。 +- `panic!` 宏表示咱们的程序处于其无法处理的状态,并让咱们告诉进程停止,而不是尝试以无效或不正确的值继续; +- `Result` 枚举使用 Rust 的类型系统来表明操作可能会失败,不过以某种咱们的代码可从中恢复的方式。咱们可使用 `Result` 告诉调用咱们代码的代码,他也需要处理潜在的成功或失败。 -在恰当情形下合理使用 `panic!` 与 `Result`,可以让咱们的代码在面对不可避免的问题时更加可靠。 +在适当的情况下使用 `panic!` 与 `Result` 将使咱们的代码在面对不可避免的问题时更加可靠。 - -现在,咱们已经看到标准库以 `Option` 和 `Result` 两个枚举,使用泛型的一些有用方法,下面我们将讨论泛型的工作原理,以及如何在咱们的代码中使用泛型。 +现在咱们已经了解了标准库通过 `Option` 和 `Result` 这两个枚举使用泛型的有用方法,我们将讨论泛型的工作原理以及咱们可以怎样在咱们的代码中使用泛型。 (End) diff --git a/src/oop/implementing.md b/src/oop/implementing.md index 341506f..e4c2aaa 100644 --- a/src/oop/implementing.md +++ b/src/oop/implementing.md @@ -1,6 +1,4 @@ -# 实现一种面向对象设计模式 - -**Implementing an Object-Oriented Design Pattern** +# 实现面向对象的设计模式 *状态模式,the state pattern* 属于一种面向对象设计模式。这种模式的核心,便是咱们要定义某个值在其内部可能有的一套各种状态。这些状态是由一套 *状态对象,state objects* 所表示的,同时该值的行为,会根据其状态而改变。接下来咱们会完成有着保存其状态字段,该字段将有着“草稿,draft”、“审阅,review” 或“已发布,published” 三种状态集合的状态对象,的一个博客帖子结构体示例。 @@ -14,11 +12,8 @@ 1. 博客帖子以一个空的草稿开始; - 2. 在草稿写好后,该帖子就要求审阅一下; - 3. 在帖子被批准后,其就会被发布; - 4. 只有发布了的帖子,才会返回要打印的内容,因此那些未获批准的帖子就不会被无故发布。 @@ -388,9 +383,6 @@ error: could not compile `simple_blog` due to previous error ### 将状态与行为编码为类型 -**Encoding States and Behavior as Types** - - 咱们将给出如何对这种状态模式加以反思,以得到一套不同的权衡取舍。不同于对状态及状态的转换进行完全地封装,进而外部代码对他们一无所知,咱们将把那些状态编码为不同类型。于是乎,Rust 的类型检查系统,就将通过发出编译器报错,阻止在那些仅允许已发布帖子之处,使用草稿帖子的尝试。 下面来考虑一下清单 17-11 中,`main` 函数的第一部分: