mirror of
https://github.com/gnu4cn/rust-lang-zh_CN.git
synced 2026-08-19 04:33:27 +08:00
Updated 'src/error_handling/panic_or_not.md'.
This commit is contained in:
17
projects/guessing_game/src/guessing_game.rs
Normal file
17
projects/guessing_game/src/guessing_game.rs
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,24 @@ fn main() {
|
||||
.read_line(&mut guess)
|
||||
.expect("读取行失败/failed to read line");
|
||||
|
||||
if guess.trim().eq("Q") || guess.trim().eq("quit") { process::exit(0); }
|
||||
if guess.trim().eq("Q") || guess.trim().eq("quit") {
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
// let guess: u32 = guess.trim().parse().expect("请输入一个数字!");
|
||||
let guess: u32 = match guess.trim().parse() {
|
||||
let guess: i32 = match guess.trim().parse()
|
||||
{
|
||||
Ok(num) => num,
|
||||
Err(_) => { println! ("请输入一个数字!"); continue },
|
||||
Err(_) => {
|
||||
println! ("请输入一个数字!");
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
println! ("你猜的数为:{}", guess);
|
||||
if guess < 1 || guess > 100 {
|
||||
println!("秘密数字将在 1 和 100 之间。");
|
||||
continue;
|
||||
}
|
||||
|
||||
match guess.cmp(&secret_number) {
|
||||
Ordering::Less => println! ("太小!"),
|
||||
|
||||
7
projects/improved_guessing_game/Cargo.toml
Normal file
7
projects/improved_guessing_game/Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "improved_guessing_game"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.9.1"
|
||||
17
projects/improved_guessing_game/src/guessing_game.rs
Normal file
17
projects/improved_guessing_game/src/guessing_game.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
pub struct Guess {
|
||||
value: i32,
|
||||
}
|
||||
|
||||
impl Guess {
|
||||
pub fn new(value: i32) -> Guess {
|
||||
if value < 1 || value > 100 {
|
||||
panic!("Guess value must be between 1 and 100, got {value}.");
|
||||
}
|
||||
|
||||
Guess { value }
|
||||
}
|
||||
|
||||
pub fn value(&self) -> i32 {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
51
projects/improved_guessing_game/src/main.rs
Normal file
51
projects/improved_guessing_game/src/main.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use rand::Rng;
|
||||
use std::{cmp::Ordering, io, process};
|
||||
|
||||
mod guessing_game;
|
||||
use crate::guessing_game::Guess;
|
||||
|
||||
fn main() {
|
||||
loop {
|
||||
println! ("\n---猜出这个数来!---");
|
||||
|
||||
let secret_number: i32 = rand::rng().random_range(1..101);
|
||||
|
||||
// println! ("随机生成的秘密数字为:{}", secret_number);
|
||||
|
||||
loop {
|
||||
println! ("请输入你猜的数。( ‘Q/quit’ 退出游戏)");
|
||||
|
||||
let mut guess: String = String::new();
|
||||
|
||||
io::stdin()
|
||||
.read_line(&mut guess)
|
||||
.expect("读取行失败/failed to read line");
|
||||
|
||||
if guess.trim().eq("Q") || guess.trim().eq("quit") {
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
// let guess: u32 = guess.trim().parse().expect("请输入一个数字!");
|
||||
let guess_number: i32 = match guess.trim().parse()
|
||||
{
|
||||
Ok(num) => num,
|
||||
Err(_) => {
|
||||
println! ("请输入一个数字!");
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
let guess: Guess = Guess::new(guess_number);
|
||||
|
||||
match guess.value().cmp(&secret_number) {
|
||||
Ordering::Less => println! ("太小!"),
|
||||
Ordering::Greater => println! ("太大!"),
|
||||
Ordering::Equal => {
|
||||
println! ("你赢了!");
|
||||
break
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,32 +56,36 @@ fn main () {
|
||||
|
||||
不过,当失败属于预期中的时,返回一个 `Result` 就比调用一次 `panic!` 更合适。例如,某个解析器得到畸形数据,或者某次 HTTP 请求返回了一个表明咱们已达速率限制的状态时。在这些情况下,返回一个 `Result` 就表明失败是预期的、调用代码必须决定如何处理的一种可能。
|
||||
|
||||
当咱们的代码执行某项在其调用代码用到无效值,则可能会给用户带来风险的操作时,咱们的代码就应首先检查这些值是有效的,并在这些值不是有效的时死机。这主要是出于安全原因:尝试在无效数据上操作,会咱们使代码暴露于漏洞之中。这是标准库会在咱们尝试越界内存访问时,将调用 `panic!` 的主要原因:尝试访问不属于当前数据结构的内存,是一项常见的安全问题。函数通常都有着 *合约,contracts*:只有在输入满足特定要求时,函数的行为才有保证。在违反合约时死机是有道理的,因为违反合约总是表明某种调用方错误,a caller-side bug,而这并不是咱们希望调用代码必须显式处理的错误类型。事实上,并没有调用代码要恢复的合理方法;调用的 *程序员* 需要修复代码。函数的合约,尤其是当违反合约时将导致死机,应在函数的 API 文档中加以说明。
|
||||
当咱们的代码执行某项若其调用的是无效值时,可能会给用户带来风险的操作时,咱们的代码就应首先检查这些值是有效的,并在值无效时死机。这主要是出于安全考虑:尝试在无效数据上操作,会使咱们的代码暴露于漏洞之中。这也是标准库会在咱们尝试越界内存访问时,调用 `panic!` 的主要原因:尝试访问不属于当前数据结构的内存,是个常见的安全问题。函数通常都有 *合约,contracts*:只有在输入满足特定要求时,他们的行为才有保证。在这种合约破坏时死机是有道理的,因为合约破坏总是表明某个调用方的错误,而这并不是咱们希望调用代码必须要显式处理的错误类型。事实上,并没有调用代码恢复的合理方式;调用的 *程序员* 需要修复代码。函数的合约,尤其是在破坏会导致死机时,应在该函数的 API 文档中加以说明。
|
||||
|
||||
|
||||
但是,在全部的函数中,进行大量错误检查,则会显得冗长而烦人。幸运的是,可使用 Rust 的类型系统(并因此由编译器完成类型检查),来完成许多的检查。在函数有着作为参数的特定类型时,就可以在知悉编译器已经确保有着有效值的情况下,着手处理代码的业务逻辑。比如,在有着一个不同于 `Option` 的类型时,程序就期望有 *某个东西(something)* 而非 *什么也没有(nothing)*。代码这时就不必处理 `Some` 与 `None` 变种的两种情形:无疑将只有一种有着某个值的情形。尝试将无值传递给该函数的代码,甚至都不会编译,那么该函数就不必在运行时对那样的情况进行检查了。另一个示例则是使用某个诸如 `u32` 无符号整数,这就确保了参数绝不会是个负数。
|
||||
然而,在所有咱们的函数中,进行大量的错误检查会显得冗长而烦人。幸运的是,咱们可使用 Rust 的类型系统(及因此由编译器完成的类型检查),为咱们完成许多的检查。若咱们的函数有某个特定类型的参数,咱们就可以在知悉编译器已确保了咱们有个有效值下,处理咱们代码的逻辑。例如,在咱们有个类型而不是 `Option` 时,那么咱们的程序就会期望得到 *某个东西,something*,而不是 *全无,nothing*。这样,咱们的代码就不必处理 `Some` 与 `None` 变种的两种情况:他只有肯定有某个值的一种情况。试图向咱们的函数传递全无的代码,甚至不会编译,因此咱们函数在运行时就不必检查这种情况。另一个例子是使用如 `u32` 的无符号整数类型,这确保参数绝不会是负数。
|
||||
|
||||
|
||||
## 创建用于验证的定制类型
|
||||
|
||||
**Creating Custom Types for Validation**
|
||||
|
||||
我们来把使用 Rust 的类型系统,确保我们有个有效值的想法向前推进一步,看看如何创建出一个用于验证的定制类型。请回顾第 2 章中的猜数游戏,其中我们的代码要求用户猜一个 `1` 到 `100` 之间的数字。在将用户的猜数与我们的秘密数字核对前,我们从未验证过其是否介于这两个数字之间;我们只验证了用户猜数是个正数。在这种情况下,后果并不严重:我们“太大”或“太小”的输出,将仍然是正确的。但是,往有效猜数引导用户,并在用户猜的数字超出范围时,及用户输入字母等时有不同的行为,这将是个非常有用的增强功能。
|
||||
|
||||
接下来将这个运用 Rust 的类型系统,来确保有着有效值的概念,进行进一步拓展,而看看创建一个用于验证的定制类型。回顾在第二章中的猜数游戏,其中的代码要求用户猜出一个 `1` 与 `100` 之间的数字。在将用户猜的数字与那里的秘密数字比对之前,是绝无对用户猜数是否处于 `1` 与 `100` 之间,进行过验证的;那里只验证过猜数为正数。在这个示例中,后果并不是非常可怕:这里的输出 “太大了” 或 “太小了” 仍将正确。但引导用户朝向有效的猜数,并在用户猜出不在该范围的数,与用户敲入了比如一些字母时,而有不同的表现,将是一项有用的功能增强。
|
||||
实现这一增强的一种方法,是将猜测值解析为 `i32`,而不是 `u32`,允许出现潜在的负数,然后添加一个数字在范围内的检查,就像这样:
|
||||
|
||||
完成此功能增强的一种方式,将是将猜数解析为一个 `i32` 而非仅仅为一个 `u32`,从而允许潜在的负数,并在随后键入一个该数位于范围中的检查,像下面这样:
|
||||
文件名:`src/main.rs`
|
||||
|
||||
```rust
|
||||
loop {
|
||||
// --跳过--
|
||||
|
||||
let guess: i32 = match guess.trim().parse() {
|
||||
let guess: i32 = match guess.trim().parse()
|
||||
{
|
||||
Ok(num) => num,
|
||||
Err(_) => { println! ("请输入一个数字!"); continue },
|
||||
Err(_) => {
|
||||
println! ("请输入一个数字!");
|
||||
continue
|
||||
},
|
||||
};
|
||||
|
||||
if guess < 1 || guess > 100 {
|
||||
println! ("秘密数字将在 1 和 100 之间");
|
||||
println!("秘密数字将在 1 和 100 之间。");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -90,11 +94,15 @@ fn main () {
|
||||
}
|
||||
```
|
||||
|
||||
其中的 `if` 表达式,对这里的值是否超出范围进行了检查,告诉用户这个问题,并调用 `continue` 来开始下一次循环迭代而请求另一个猜数。在这个 `if` 表达式之后,就可继续进行 `guess` 与秘密数字之间的比较,获悉 `guess` 是在 `1` 与 `100` 之间。
|
||||
|
||||
然而这并非一种理想的方案:若程序只运行在 `1` 与 `100` 之间的值这一点至关重要,且程序有着许多有此要求的函数,而在每个函数中都进行这样的一个检查,就会显得冗长乏味(并可能影响性能)。
|
||||
其中 `if` 表达式会检查我们的值是否超出范围,告诉用户问题所在,并调用 `continue` 开始循环的下一次迭代并请求另一个猜数。在这个 `if` 表达式后,我们可以在清楚 `guess` 介于 `1` 和 `100` 之间下,继续 `guess` 与秘密数字间的比较。
|
||||
|
||||
相反,这里可以构造一种新类型,并将那些验证放入某个函数,从而创建出该类型的一个示例,而非在各个地方重复这些验证。那样的话,这些函数就可以在他们的签名中,安全地使用这种新类型,并信心十足地使用他们接收到的那些值了。下面清单 9-13 给出了一种定义 `Guess` 类型的方式,在 `new` 函数接收到一个 `1` 与 `100` 之间的值时,这种方式下将只创建一个 `Guess` 的实例。
|
||||
然而,这不是一种理想解决方案:在程序只对 `1` 到 `100` 间的值进行操作绝对重要,并且程序有着许多有此要求的函数,那么在每个函数中都进行这样的检查,将是非常乏味的(而且可能会影响性能)。
|
||||
|
||||
相反,我们可在一个专门模组中构造一个新类型,并把这些验证放入一个函数中,从而创建出该类型的实例,而不是在各处重复这些验证。这样,函数就可以安全地在其签名中使用这个新类型,并放心地使用接收到的值。下面清单 9-13 展示了定义 `Guess` 类型的一种方法,只有在 `new` 函数接收到介于 `1` 和 `100` 之间的值时,才会创建出一个 `Guess` 的实例。
|
||||
|
||||
|
||||
文件名:`src/guessing_game.rs`
|
||||
|
||||
```rust
|
||||
pub struct Guess {
|
||||
@@ -104,7 +112,7 @@ pub struct Guess {
|
||||
impl Guess {
|
||||
pub fn new(value: i32) -> Guess {
|
||||
if value < 1 || value > 100 {
|
||||
panic! ("Guess 类型值必须在 1 与 100 之间,收到的是 {}", value);
|
||||
panic!("猜数值必须在 1 与 100 之间,得到了 {value}。");
|
||||
}
|
||||
|
||||
Guess { value }
|
||||
@@ -116,23 +124,30 @@ impl Guess {
|
||||
}
|
||||
```
|
||||
|
||||
*清单 9-13:只会在值处于 `1` 与 `100` 之间,才继续执行的一个 `Guess` 类型*
|
||||
*清单 9-13:只有在处于 `1` 与 `100` 之间的值下才会继续的 `Guess` 类型*
|
||||
|
||||
首先,我们创建了个名为 `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`,带有一个叫做 `value`、保存了一个 `i32` 值字段的结构体。这就是要存储数字的地方。
|
||||
接下来,我们实现了一个名为 `value` 的方法,该方法借用了 `self`,不带任何其他参数,并返回一个 `i32`。这种类别的方法,有时被称为 *getter*,因为他的目的是获取其字段中的某个数据并返回。这个公共方法是必要的,因为 `Guess` 结构体的 `value` 字段是私有的。`value` 这个字段是私有的很重要,这样使用 `Guess` 这个结构体的代码,就不能直接设置 `value`:`guessing_game` 模组之外的代码,*必须* 使用 `Guess::new` 函数创建 `Guess` 的实例,从而确保某个 `Guess` 不可能有着一个,未经 `Guess::new` 函数中条件检查过的 `value`。
|
||||
|
||||
随后这里在 `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` 类型值。
|
||||
某个有着参数或返回值仅为 `1` 到 `100` 之间数字的函数,随后就可以在其签名中,声明他接收或返回的是 `Guess` 而不是 `i32`,并且不需要在其主体中进行任何额外的检查。
|
||||
|
||||
接下来,这里实现了一个名为 `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` 就会令到咱们的代码在各种不可避免的问题面前,更加可靠。
|
||||
Rust 的这些错误处理特性,旨在帮助咱们编写更健壮的代码。
|
||||
|
||||
既然这里已经见识到标准库在 `Option` 与 `Result` 枚举上,运用到泛型的一些有用方式,那么接下来就要谈及泛型的原理,以及怎样在咱们的代码中运用泛型。
|
||||
- `panic!` 这个宏表示咱们的程序处于其无法处理的某种状态,让咱们可以告诉进程停止,而不是尝试继续处理无效或不正确的值;
|
||||
- `Result` 枚举使用 Rust 的类型系统,表示可能会失败,但咱们代码可从中恢复的那些操作。咱们可以使用 `Result` 告诉调用咱们代码的代码,他也需要处理潜在的成功或失败。
|
||||
|
||||
在恰当情形下合理使用 `panic!` 与 `Result`,可以让咱们的代码在面对不可避免的问题时更加可靠。
|
||||
|
||||
|
||||
现在,咱们已经看到标准库以 `Option` 和 `Result` 两个枚举,使用泛型的一些有用方法,下面我们将讨论泛型的工作原理,以及如何在咱们的代码中使用泛型。
|
||||
|
||||
|
||||
(End)
|
||||
|
||||
Reference in New Issue
Block a user