From 1a8cac0ab576c6e3ae512e1380fbdd7934fc4570 Mon Sep 17 00:00:00 2001 From: Hector PENG Date: Mon, 30 Jun 2025 16:46:31 +0800 Subject: [PATCH] Updated 'src/error_handling/panic_or_not'. --- projects/panic_or_not/Cargo.toml | 6 +++ projects/panic_or_not/src/main.rs | 9 +++++ src/error_handling/panic_or_not.md | 37 +++++++++---------- .../packages_and_crates.md | 2 +- 4 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 projects/panic_or_not/Cargo.toml create mode 100644 projects/panic_or_not/src/main.rs diff --git a/projects/panic_or_not/Cargo.toml b/projects/panic_or_not/Cargo.toml new file mode 100644 index 0000000..c7b53db --- /dev/null +++ b/projects/panic_or_not/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "panic_or_not" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/projects/panic_or_not/src/main.rs b/projects/panic_or_not/src/main.rs new file mode 100644 index 0000000..c130ce0 --- /dev/null +++ b/projects/panic_or_not/src/main.rs @@ -0,0 +1,9 @@ +use std::net::IpAddr; + +fn main () { + let home: IpAddr = "127.0.0.1" + .parse() + .expect("硬编码的 IP 地址应是有效的"); + + println! ("{:?}", home); +} diff --git a/src/error_handling/panic_or_not.md b/src/error_handling/panic_or_not.md index 790efe9..16adb89 100644 --- a/src/error_handling/panic_or_not.md +++ b/src/error_handling/panic_or_not.md @@ -2,30 +2,29 @@ **To `panic!` or Not to `panic!`** +那么,当代码死机无法恢复时,咱们要如何决定何时应该调用 `panic!`,何时应返回 `Result` 呢?对于任何错误情形,无论是否有可能恢复,咱们都可以调用 `panic!`,但这样一来,咱们就代表调用代码,做出了无法恢复的决定。在咱们选择返回一个 `Result` 值时,咱们就给了调用代码一些选择。调用代码可以选择以适合其情况的方式尝试恢复,或者他可以决定在这种情况下的某个 `Err` 值是不可恢复的,因此他可以调用 `panic!` 并将咱们的可恢复错误,变成一个不可恢复的错误。因此,在咱们定义某个可能失败的函数时,返回 `Result` 是一个不错的默认选择。 -那么该怎样确定,什么时候应该调用 `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)。 +在示例程序、原型代码及测试等情况下,编写会死机而非返回一个 `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` 这样的可能会中止程序运行方法的调用,确信就表明那是一个是要这个应用程序对错误进行处理的占位符,而根据接下来代码所做的事情,这样的调用会有所不同。 +同样,在咱们原型设计时,准备好决定如何处理错误前,`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` 值的其他某些逻辑,但编译器对此逻辑却一无所知时,调用 `unwrap` 或 `expect` 也是恰当的。这时将仍然有个需要处理的 `Result` 值:对于不论所调用的什么操作,即使在当前特定情形下,逻辑上失败绝无可能,但所调用的操作原本总体上仍是有失败可能。在经由亲自检查代码,而能确保绝不会有 `Err` 变种时,调用 `unwrap` 就是完美可接受的,且将自己设想的绝不会有 `Err` 变种的原因,在 `expect` 文本中撰写出来,这样做甚至更佳。下面就是一个示例: ```rust fn main () { @@ -37,28 +36,28 @@ fn main () { } ``` -这里是在通过解析硬编码字符串,创建一个 `IpAddr`。可以看到 `127.0.0.1` 是个有效的 IP 地址,因此这里使用 `expect` 是可接受的。然而,有着一个硬编码的、有效的字符串,并未改变 `parse` 方法返回值类型:这里仍将得到一个 `Result` 类型,同时由于编译器不是足够聪明到发现这个字符串总是个有效的 IP 地址,那么编译器仍将要求,以 `Err` 变种是一种可能性那样,对这个 `Result` 进行处理。在这个 IP 地址为来自用户输入,而非这里的硬编码到程序中,进而 *确实* 有着失败可能时,无疑就要打算对这个 `Result` 进行更为健壮的处理了。这种提及这个 IP 地址为硬编码的假设,将提醒到在将来,需要从其他来源获取这个 IP 地址时,就要把 `expect` 修改为更好的错误处理代码。 + +通过解析一个硬编码字符串,我们创建出一个 `IpAddr` 实例。我们可以看到,`127.0.0.1` 是个有效的 IP 地址,因此在这里使用 `expect` 是可接受的。然而,有着一个硬编码的有效字符串,并不会改变 `parse` 这个方法的返回类型:我们仍会得到一个 `Result` 值,同时编译器仍将让我们按照 `Err` 变种为一种可能性一样,处理这个 `Result`,因为编译器还不够聪明,无法发现这个字符串始终是个有效的 IP 地址。在这个 IP 地址字符串是来自用户,而不是硬编码到程序中的时,就因此 *确实* 存在失败的可能性,那么我们肯定就会希望以一种更健壮的方式,处理这个 `Result`。在将来我们需要从其他来源获取该 IP 地址时,那么提及这个 IP 地址是硬编码的假设,将提醒我们将 `expect` 修改成更好的错误处理代码。 -## 错误处理准则 +## 错误处理指南 **Guidelines for Error Handling** +当咱们的代码有可能陷入某种糟糕的状态时,那么最好让咱们的代码死机。在此语境下,所谓 *糟糕状态,bad state* 是指某个假定、保证、合约,或恒定值被破坏,例如在无效值、矛盾值或缺失值被传递给咱们的代码时 -- 再加上以下一种或多种情况: -在代码可能以糟糕状态结束运行时,那么让代码中止运行就是明智的。在这种情形下,所谓 *糟糕状态(a bad state)* 就是在某种假设、保证、合约,或恒值已被破坏,譬如在无效值、矛盾值,或缺失值被传递到所编写代码 -- 加上以下的一项或多项: +- 糟糕状态是指意想不到的某种情况,与偶尔会发生的情况相反,比如错误格式的用户输入数据等; -- 糟糕状态是某些不期望的东西,他们与偶发的东西相反,比如用户输入的错误格式数据; +- 咱们此处之后的代码,需要依赖于不再处于这种不良状态,而不是在每一步都检查问题; -- 在此处之后的代码,需要依赖于不处在这种糟糕状态,而不是在接下来的每一步都检查这个问题; +- 并无以咱们所使用类型,编码这些信息的某种良好方式。我们将在第 18 章 [“将状态和行为编码为类型”](../oop/implementing.md#将状态与行为编码为类型) 中,举例说明这个意思。 -- 没有以自己所使用的类型,来编码该信息的好办法。在第 17 章的 [“将状态与行为编码为类型”](Ch17_Object_Oriented_Programming_Features_of_Rust.md#将状态与行为当作类型编码) 小节,就会贯穿一个这里所意指的示例。 +如果有人调用了咱们的代码并传入不合理的值,那么最好是咱们尽可能返回某个错误,这样该库的用户就可以决定,他们在这种情况下要做什么。但是,如果继续下去可能不安全或有害,那么最好的选择就可能是调用 `panic!`,并提醒使用咱们库的人他们代码中的错误,以便他们在开发过程中修复错误。类似地,若咱们调用的外部代码不在咱们的掌控中,而他返回了咱们无法修复的某种无效状态,这时通常也适合调用 `panic!`。 +不过,当失败属于预期中的时,返回一个 `Result` 就比调用一次 `panic!` 更合适。例如,某个解析器得到畸形数据,或者某次 HTTP 请求返回了一个表明咱们已达速率限制的状态时。在这些情况下,返回一个 `Result` 就表明失败是预期的、调用代码必须决定如何处理的一种可能。 -在有人调用到咱们的代码,并传入了无意义的值时,在可以的情况下,最好返回一个错误,这样库用户就可以确定在那样的情况下,他们打算做什么。然而在继续执行下去会不安全或有危害的情形中,那么最佳选择就会时调用 `panic!`,并警醒使用到咱们库的人他们代码中的错误,这样在他们开发过程中就可以修好那个代码错误。与此类似,在调用不在掌控中的外部代码,且该外部代码返回了无法修复的无效状态时,那么 `panic!` 通常就是恰当选择。 +当咱们的代码执行某项在其调用代码用到无效值,则可能会给用户带来风险的操作时,咱们的代码就应首先检查这些值是有效的,并在这些值不是有效的时死机。这主要是出于安全原因:尝试在无效数据上操作,会咱们使代码暴露于漏洞之中。这是标准库会在咱们尝试越界内存访问时,将调用 `panic!` 的主要原因:尝试访问不属于当前数据结构的内存,是一项常见的安全问题。函数通常都有着 *合约,contracts*:只有在输入满足特定要求时,函数的行为才有保证。在违反合约时死机是有道理的,因为违反合约总是表明某种调用方错误,a caller-side bug,而这并不是咱们希望调用代码必须显式处理的错误类型。事实上,并没有调用代码要恢复的合理方法;调用的 *程序员* 需要修复代码。函数的合约,尤其是当违反合约时将导致死机,应在函数的 API 文档中加以说明。 -不过在失败为预期的时,那么相比于构造一个 `panic!` 调用,返回一个 `Result` 则更为恰当。这类示例包括给到解析器错误格式数据,或某个返回了表示已达到访问数限制的 HTTP 请求等。在这些情况下,返回一个 `Result` 就表示失败是一种调用代码必须确定如何处理的预期可能。 - -在所编写代码被使用无效值调用,而执行了某种可能将用户置于危险境地的操作时,那么代码就应首先对这些值进行检查,并在这些值无效时中止运行。这主要是处于安全原因:尝试运行于无效数据,就会将代码暴露于漏洞。这就是在尝试超出边界的内存访问时,标准库会调用 `panic!` 的主要原因:尝试访问不属于当前数据结构的内存,是个常见的安全问题。函数通常有着 *合约(contracts)*:只在输入满足特定要求时,他们的行为才有保证。那么由于合约破坏总是表明调用者侧的代码错误,且这种错误并非是要调用代码必须显式处理的那种错误,因此在合约被破坏时的中止运行就说得通了。实际上,调用代码是没有恢复的合理方法的;调用的 *代码编写者* 需要修复该代码。应在函数的 API 文档中,解释函数的合约,尤其是在合约破坏会导致中止运行时。 但是,在全部的函数中,进行大量错误检查,则会显得冗长而烦人。幸运的是,可使用 Rust 的类型系统(并因此由编译器完成类型检查),来完成许多的检查。在函数有着作为参数的特定类型时,就可以在知悉编译器已经确保有着有效值的情况下,着手处理代码的业务逻辑。比如,在有着一个不同于 `Option` 的类型时,程序就期望有 *某个东西(something)* 而非 *什么也没有(nothing)*。代码这时就不必处理 `Some` 与 `None` 变种的两种情形:无疑将只有一种有着某个值的情形。尝试将无值传递给该函数的代码,甚至都不会编译,那么该函数就不必在运行时对那样的情况进行检查了。另一个示例则是使用某个诸如 `u32` 无符号整数,这就确保了参数绝不会是个负数。 diff --git a/src/packages_crates_and_modules/packages_and_crates.md b/src/packages_crates_and_modules/packages_and_crates.md index 51e694b..bc96125 100644 --- a/src/packages_crates_and_modules/packages_and_crates.md +++ b/src/packages_crates_and_modules/packages_and_crates.md @@ -3,7 +3,7 @@ **Packages and Crates** -我们要介绍的模块系统的头两个部分,是包与代码箱。 +我们要介绍的模组系统头两个部分,分别是代码包与代码箱。 *代码箱,crate* 是 Rust 编译器一次要考虑的最小代码量。即使咱们运行的是 `rustc` 而不是 `cargo`,并只传递了一个源代码文件(就像我们在第 1 章 “编写和运行 Rust 程序” 小节中所做的那样),编译器也会将该文件,视为一个代码箱。代码箱可以包含模组,模组也可以定义在与该代码箱一起编译的其他文件中,正如我们将在接下来的小节中看到的那样。