check to ch09-03

This commit is contained in:
KaiserY 2019-11-21 01:03:07 +08:00
parent 347607f597
commit ba860c10c9
13 changed files with 136 additions and 118 deletions

View File

@ -4,4 +4,4 @@
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
*struct*,或者 *structure*,是一个自定义数据类型,允许你命名和包装多个相关的值,从而形成一个有意义的组合。如果你熟悉一门面向对象语言,*struct* 就像对象中的数据属性。在本章中,我们会对比元组与结构体的异同,演示结构体的用法,并讨论如何在结构体上定义方法和关联函数来指定与结构体数据相关的行为。你在程序中基于结构体和枚举(*enum*)(在第六章介绍)创建新类型,以充分利用 Rust 的编译时类型检查。
*struct*,或者 *structure*,是一个自定义数据类型,允许你命名和包装多个相关的值,从而形成一个有意义的组合。如果你熟悉一门面向对象语言,*struct* 就像对象中的数据属性。在本章中,我们会对比元组与结构体的异同,演示结构体的用法,并讨论如何在结构体上定义方法和关联函数来指定与结构体数据相关的行为。你可以在程序中基于结构体和枚举(*enum*)(在第六章介绍)创建新类型,以充分利用 Rust 的编译时类型检查。

View File

@ -2,7 +2,7 @@
> [ch05-01-defining-structs.md](https://github.com/rust-lang/book/blob/master/src/ch05-01-defining-structs.md)
> <br>
> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
> commit f617d58c1a88dd2912739a041fd4725d127bf9fb
结构体和我们在第三章讨论过的元组类似。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这些名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。
@ -176,7 +176,7 @@ let user2 = User {
也可以定义与元组(在第三章讨论过)类似的结构体,称为 **元组结构体***tuple structs*)。元组结构体有着结构体名称提供的含义,但没有具体的字段名,只有字段的类型。当你想给整个元组取一个名字,并使元组成为与其他元组不同的类型时,元组结构体是很有用的,这时像常规结构体那样为每个字段命名就显得多余和形式化了。
定义元组结构体,以 `struct` 关键字和结构体名开头并后跟元组中的类型。例如,下面是两个分别叫做 `Color``Point` 元组结构体的定义和用法:
定义元组结构体,以 `struct` 关键字和结构体名开头并后跟元组中的类型。例如,下面是两个分别叫做 `Color``Point` 元组结构体的定义和用法:
```rust
struct Color(i32, i32, i32);

View File

@ -2,7 +2,7 @@
> [ch05-02-example-structs.md](https://github.com/rust-lang/book/blob/master/src/ch05-02-example-structs.md)
> <br>
> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
> commit 9cb1d20394f047855a57228dc4cbbabd0a9b395a
为了理解何时会需要使用结构体,让我们编写一个计算长方形面积的程序。我们会从单独的变量开始,接着重构程序直到使用结构体替代他们为止。
@ -42,7 +42,7 @@ The area of the rectangle is 1500 square pixels.
fn area(width: u32, height: u32) -> u32 {
```
函数 `area` 本应该计算一个长方形的面积,不过函数却有两个参数。这两个参数是相关联的,不过程序本身却没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。第三章的 “元组类型” 部分已经讨论过了一种可行的方法:元组。
函数 `area` 本应该计算一个长方形的面积,不过函数却有两个参数。这两个参数是相关联的,不过程序本身却没有表现出这一点。将长度和宽度组合在一起将更易懂也更易处理。第三章的 [“元组类型”][the-tuple-type] 部分已经讨论过了一种可行的方法:元组。
### 使用元组重构
@ -129,7 +129,7 @@ fn main() {
当我们运行这个代码时,会出现带有如下核心信息的错误:
```text
error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied
error[E0277]: `Rectangle` doesn't implement `std::fmt::Display`
```
`println!` 宏能处理很多类型的格式,不过,`{}` 默认告诉 `println!` 使用被称为 `Display` 的格式:意在提供给直接终端用户查看的输出。目前为止见过的基本类型都默认实现了 `Display`,因为它就是向用户展示 `1` 或其他任何基本类型的唯一方式。不过对于结构体,`println!` 应该用来输出的格式是不明确的因为这有更多显示的可能性是否需要逗号需要打印出大括号吗所有字段都应该显示吗由于这种不确定性Rust 不会尝试猜测我们的意图,所以结构体并没有提供一个 `Display` 实现。
@ -137,8 +137,8 @@ error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied
但是如果我们继续阅读错误,将会发现这个有帮助的信息:
```text
`Rectangle` cannot be formatted with the default formatter; try using
`:?` instead if you are using a format string
= help: the trait `std::fmt::Display` is not implemented for `Rectangle`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
```
让我们来试试!现在 `println!` 宏调用看起来像 `println!("rect1 is {:?}", rect1);` 这样。在 `{}` 中加入 `:?` 指示符告诉 `println!` 我们想要使用叫做 `Debug` 的输出格式。`Debug` 是一个 trait它允许我们以一种对开发者有帮助的方式打印结构体以便当我们调试代码时能看到它的值。
@ -146,14 +146,14 @@ error[E0277]: the trait bound `Rectangle: std::fmt::Display` is not satisfied
这样调整后再次运行程序。见鬼了!仍然能看到一个错误:
```text
error[E0277]: the trait bound `Rectangle: std::fmt::Debug` is not satisfied
error[E0277]: `Rectangle` doesn't implement `std::fmt::Debug`
```
不过编译器又一次给出了一个有帮助的信息:
```text
`Rectangle` cannot be formatted using `:?`; if it is defined in your
crate, add `#[derive(Debug)]` or manually implement it
= help: the trait `std::fmt::Debug` is not implemented for `Rectangle`
= note: add `#[derive(Debug)]` or manually implement `std::fmt::Debug`
```
Rust **确实** 包含了打印出调试信息的功能,不过我们必须为结构体显式选择这个功能。为此,在结构体定义之前加上 `#[derive(Debug)]` 注解,如示例 5-12 所示:
@ -194,3 +194,5 @@ rect1 is Rectangle {
Rust 为我们提供了很多可以通过 `derive` 注解来使用的 trait他们可以为我们的自定义类型增加实用的行为。附录 C 中列出了这些 trait 和行为。第十章会介绍如何通过自定义行为来实现这些 trait同时还有如何创建你自己的 trait。
我们的 `area` 函数是非常特殊的,它只计算长方形的面积。如果这个行为与 `Rectangle` 结构体再结合得更紧密一些就更好了,因为它不能用于其他类型。现在让我们看看如何继续重构这些代码,来将 `area` 函数协调进 `Rectangle` 类型定义的 `area` **方法** 中。
[the-tuple-type]: ch03-02-data-types.html#the-tuple-type

View File

@ -2,8 +2,8 @@
> [ch06-00-enums.md](https://github.com/rust-lang/book/blob/master/src/ch06-00-enums.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit a5a03d8f61a5b2c2111b21031a3f526ef60844dd
本章介绍 **枚举***enumerations*),也被称作 *enums*。枚举允许你通过列举可能的来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做 `Option`,它代表一个值要么是某个值要么什么都不是。然后会讲到在 `match` 表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。最后会介绍 `if let`,另一个简洁方便处理代码中枚举的结构。
本章介绍 **枚举***enumerations*),也被称作 *enums*。枚举允许你通过列举可能的 **成员***variants* 来定义一个类型。首先,我们会定义并使用一个枚举来展示它是如何连同数据一起编码信息的。接下来,我们会探索一个特别有用的枚举,叫做 `Option`,它代表一个值要么是某个值要么什么都不是。然后会讲到在 `match` 表达式中用模式匹配,针对不同的枚举值编写相应要执行的代码。最后会介绍 `if let`,另一个简洁方便处理代码中枚举的结构。
枚举是一个很多语言都有的功能不过不同语言中其功能各不相同。Rust 的枚举与 F#、OCaml 和 Haskell 这样的函数式编程语言中的 **代数数据类型***algebraic data types*)最为相似。

View File

@ -2,7 +2,7 @@
> [ch06-01-defining-an-enum.md](https://github.com/rust-lang/book/blob/master/src/ch06-01-defining-an-enum.md)
> <br>
> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
> commit a5a03d8f61a5b2c2111b21031a3f526ef60844dd
让我们看看一个需要诉诸于代码的场景,来考虑为何此时使用枚举更为合适且实用。假设我们要处理 IP 地址。目前被广泛使用的两个主要 IP 标准IPv4version four和 IPv6version six。这是我们的程序可能会遇到的所有可能的 IP 地址类型:所以可以 **枚举** 出所有可能的值,这也正是此枚举名字的由来。
@ -265,7 +265,7 @@ not satisfied
|
```
哇哦!事实上,错误信息意味着 Rust 不知道该如何将 `Option<i8>``i8` 相加,因为它们的类型不同。当在 Rust 中拥有一个像 `i8` 这样类型的值时,编译器确保它总是有一个有效的值。我们可以自信使用而无需做空值检查。只有当使用 `Option<i8>`(或者任何用到的类型)的时候需要担心可能没有值,而编译器会确保我们在使用值之前处理了为空的情况。
很好!事实上,错误信息意味着 Rust 不知道该如何将 `Option<i8>``i8` 相加,因为它们的类型不同。当在 Rust 中拥有一个像 `i8` 这样类型的值时,编译器确保它总是有一个有效的值。我们可以自信使用而无需做空值检查。只有当使用 `Option<i8>`(或者任何用到的类型)的时候需要担心可能没有值,而编译器会确保我们在使用值之前处理了为空的情况。
换句话说,在对 `Option<T>` 进行 `T` 的运算之前必须将其转换为 `T`。通常这能帮助我们捕获到空值最常见的问题之一:假设某值不为空但实际上为空的情况。

View File

@ -2,13 +2,13 @@
> [ch06-02-match.md](https://github.com/rust-lang/book/blob/master/src/ch06-02-match.md)
> <br>
> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
> commit b374e75f1d7b743c84a6bb1ef72579a6588bcb8a
Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及它们的作用。`match` 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
可以把 `match` 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会通过 `match` 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。
因为刚刚提到了硬币,让我们用它们来作为一个使用 `match` 的例子!我们可以编写一个函数来获取一个未知的(美帝)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如示例 6-3 中所示
因为刚刚提到了硬币,让我们用它们来作为一个使用 `match` 的例子!我们可以编写一个函数来获取一个未知的(美帝)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如示例 6-3 中所示
```rust
enum Coin {
@ -18,7 +18,7 @@ enum Coin {
Quarter,
}
fn value_in_cents(coin: Coin) -> u32 {
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
@ -48,7 +48,7 @@ fn value_in_cents(coin: Coin) -> u32 {
# Quarter,
# }
#
fn value_in_cents(coin: Coin) -> u32 {
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
@ -103,7 +103,7 @@ enum Coin {
# Quarter(UsState),
# }
#
fn value_in_cents(coin: Coin) -> u32 {
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,

View File

@ -2,7 +2,7 @@
> [ch07-02-defining-modules-to-control-scope-and-privacy.md](https://github.com/rust-lang/book/blob/master/src/ch07-02-defining-modules-to-control-scope-and-privacy.md)
> <br>
> commit 879fef2345bf32751a83a9e779e0cb84e79b6d3d
> commit 34b089627cca09a73ce92a052222304bff0056e3
在本节,我们将讨论模块和其它一些关于模块系统的部分,如允许你命名项的 *路径**paths*);用来将路径引入作用域的 `use` 关键字;以及使项变为公有的 `pub` 关键字。我们还将讨论 `as` 关键字、外部包和 glob 运算符。现在,让我们把注意力放在模块上!

View File

@ -2,7 +2,7 @@
> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/master/src/ch08-01-vectors.md)
> <br>
> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
> commit 76df60bccead5f3de96db23d97b69597cd8a2b82
我们要讲到的第一个类型是 `Vec<T>`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个的值它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
@ -117,21 +117,21 @@ println!("The first element is: {}", first);
```text
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:10:5
|
8 | let first = &v[0];
| - immutable borrow occurs here
9 |
10 | v.push(6);
| ^^^^^^^^^ mutable borrow occurs here
11 |
12 | println!("The first element is: {}", first);
| ----- borrow later used here
--> src/main.rs:6:5
|
4 | let first = &v[0];
| - immutable borrow occurs here
5 |
6 | v.push(6);
| ^^^^^^^^^ mutable borrow occurs here
7 |
8 | println!("The first element is: {}", first);
| ----- immutable borrow later used here
```
示例 8-7 中的代码看起来应该能够运行:为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector 的工作方式:在 vector 的结尾增加新元素时,在没有足够空间将所有所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。
> 注意:关于 `Vec<T>` 类型的更多实现细节,在 *https://doc.rust-lang.org/stable/nomicon/vec.html* 查看 “The Nomicon”
> 注意:关于 `Vec<T>` 类型的更多实现细节,在 https://doc.rust-lang.org/stable/nomicon/vec.html 查看 “The Nomicon”
### 遍历 vector 中的元素
@ -157,7 +157,7 @@ for i in &mut v {
<span class="caption">示例8-9遍历 vector 中元素的可变引用</span>
为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `i` 中的值。第十五章会详细介绍 `*`
为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `i` 中的值。第十五章的 [“通过解引用运算符追踪指针的值”][deref] 部分会详细介绍解引用运算符
### 使用枚举来储存多种类型
@ -186,3 +186,5 @@ Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需
如果在编写程序时不能确切无遗地知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,第十七章会讲到它。
现在我们了解了一些使用 vector 的最常见的方式,请一定去看看标准库中 `Vec` 定义的很多其他实用方法的 API 文档。例如,除了 `push` 之外还有一个 `pop` 方法,它会移除并返回 vector 的最后一个元素。让我们继续下一个集合类型:`String`
[deref]: ch15-02-deref.html#following-the-pointer-to-the-value-with-the-dereference-operator

View File

@ -2,7 +2,7 @@
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/src/ch08-02-strings.md)
> <br>
> commit a86c1d315789b3ca13b20d50ad5005c62bdd9e37
> commit c084bdd9ee328e7e774df19882ccc139532e53d8
第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域这是由于三方面理由的结合Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些要素结合起来对于来自其他语言背景的程序员就可能显得很困难了。
@ -169,7 +169,7 @@ let h = s1[0];
<span class="caption">示例 8-19尝试对字符串使用索引语法</span>
会导致如下错误:
这段代码会导致如下错误:
```text
error[E0277]: the trait bound `std::string::String: std::ops::Index<{integer}>` is not satisfied
@ -185,7 +185,7 @@ error[E0277]: the trait bound `std::string::String: std::ops::Index<{integer}>`
#### 内部表现
`String` 是一个 `Vec<u8>` 的封装。让我们看看之前一些正确编码的字符串的例子。首先是这一个:
`String` 是一个 `Vec<u8>` 的封装。让我们看看示例 8-14 中一些正确编码的字符串的例子。首先是这一个:
```rust
let len = String::from("Hola").len();
@ -204,7 +204,7 @@ let hello = "Здравствуйте";
let answer = &hello[0];
```
`answer` 的值应该是什么呢?它应该是第一个字符 `З` 吗?当使用 UTF-8 编码时,`З` 的第一个字节 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回,即便这个字符串只有拉丁字母: 即便 `&"hello"[0]` 是返回字节值的有效代码,它也应当返回 `104` 而不是 `h`。为了避免返回意想不到值并造成不能立刻发现的 bug。Rust 选择不编译这些代码并及早杜绝了误会的发生。
`answer` 的值应该是什么呢?它应该是第一个字符 `З` 吗?当使用 UTF-8 编码时,`З` 的第一个字节 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回,即便这个字符串只有拉丁字母: 即便 `&"hello"[0]` 是返回字节值的有效代码,它也应当返回 `104` 而不是 `h`。为了避免返回意想不到值并造成不能立刻发现的 bug。Rust 根本不会编译这些代码并在开发过程中及早杜绝了误会的发生。
#### 字节、标量值和字形簇!天呐!
@ -213,8 +213,7 @@ let answer = &hello[0];
比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 vector 中的 `u8` 值看起来像这样:
```text
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]
[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164, 224, 165, 135]
```
这里有 18 个字节,也就是计算机最终会储存的数据。如果从 Unicode 标量值的角度理解它们,也就像 Rust 的 `char` 类型那样,这些字节看起来像这样:
@ -245,7 +244,7 @@ let s = &hello[0..4];
这里,`s` 会是一个 `&str`,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着 `s` 将会是 “Зд”。
如果获取 `&hello[0..1]` 会发生什么呢?答案是:在运行时会 panic就跟访问 vector 中的无效索引时一样:
如果获取 `&hello[0..1]` 会发生什么呢?答案是:Rust 在运行时会 panic就跟访问 vector 中的无效索引时一样:
```text
thread 'main' panicked at 'byte index 1 is not a char boundary; it is inside 'З' (bytes 0..2) of `Здравствуйте`', src/libcore/str/mod.rs:2188:4

View File

@ -2,7 +2,7 @@
> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/master/src/ch08-03-hash-maps.md)
> <br>
> commit d073ece693e880b69412e645e4eabe99e74e7590
> commit 85b02530cc749565c26c05bf1b3a838334e9717f
最后介绍的常用集合类型是 **哈希 map***hash map*)。`HashMap<K, V>` 类型储存了一个键类型 `K` 对应一个值类型 `V` 的映射。它通过一个 **哈希函数***hashing function*来实现映射决定如何将键和值放入内存中。很多编程语言支持这种数据结构不过通常有不同的名字哈希、map、对象、哈希表或者关联数组仅举几例。
@ -12,7 +12,7 @@
### 新建一个哈希 map
可以使用 `new` 创建一个空的 `HashMap`,并使用 `insert` 增加元素。这里我们记录两支队伍的分数,分别是蓝队和黄队。蓝队开始有 10 分而黄队开始有 50 分:
可以使用 `new` 创建一个空的 `HashMap`,并使用 `insert` 增加元素。在示例 8-20 中我们记录两支队伍的分数,分别是蓝队和黄队。蓝队开始有 10 分而黄队开始有 50 分:
```rust
use std::collections::HashMap;
@ -27,7 +27,7 @@ scores.insert(String::from("Yellow"), 50);
注意必须首先 `use` 标准库中集合部分的 `HashMap`。在这三个常用集合中,`HashMap` 是最不常用的,所以并没有被 prelude 自动引用。标准库中对 `HashMap` 的支持也相对较少,例如,并没有内建的构建宏。
像 vector 一样,哈希 map 将它们的数据储存在堆上,这个 `HashMap` 的键类型是 `String` 而值类型是 `i32`同样类似于 vector哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。
像 vector 一样,哈希 map 将它们的数据储存在堆上,这个 `HashMap` 的键类型是 `String` 而值类型是 `i32`。类似于 vector哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。
另一个构建哈希 map 的方法是使用一个元组的 vector 的 `collect` 方法,其中每个元组包含一个键值对。`collect` 方法可以将数据收集进一系列的集合类型,包括 `HashMap`。例如,如果队伍的名字和初始分数分别在两个 vector 中,可以使用 `zip` 方法来创建一个元组的 vector其中 “Blue” 与 10 是一对,依此类推。接着就可以使用 `collect` 方法将这个元组 vector 转换成一个 `HashMap`,如示例 8-21 所示:
@ -64,7 +64,7 @@ map.insert(field_name, field_value);
`insert` 调用将 `field_name``field_value` 移动到哈希 map 中后,将不能使用这两个绑定。
如果将值的引用插入哈希 map这些值本身将不会被移动进哈希 map。但是这些引用指向的值必须至少在哈希 map 有效时也是有效的。第十章 “使用生命周期保证引用有效” 部分将会更多的讨论这个问题。
如果将值的引用插入哈希 map这些值本身将不会被移动进哈希 map。但是这些引用指向的值必须至少在哈希 map 有效时也是有效的。第十章 [“生命周期与引用有效性”][validating-references-with-lifetimes] 部分将会更多的讨论这个问题。
### 访问哈希 map 中的值
@ -193,3 +193,6 @@ vector、字符串和哈希 map 会在你的程序需要储存、访问和修改
标准库 API 文档中描述的这些类型的方法将有助于你进行这些练习!
我们已经开始接触可能会有失败操作的复杂程序了,这也意味着接下来是一个了解错误处理的绝佳时机!
[validating-references-with-lifetimes]:
ch10-03-lifetime-syntax.html#validating-references-with-lifetimes

View File

@ -2,7 +2,7 @@
> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-01-unrecoverable-errors-with-panic.md)
> <br>
> commit d073ece693e880b69412e645e4eabe99e74e7590
> commit 426f3e4ec17e539ae9905ba559411169d303a031
突然有一天代码出问题了而你对此束手无策。对于这种情况Rust 有 `panic!`宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。
@ -30,15 +30,15 @@ fn main() {
```text
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.25 secs
Finished dev [unoptimized + debuginfo] target(s) in 0.25s
Running `target/debug/panic`
thread 'main' panicked at 'crash and burn', src/main.rs:2:4
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.
```
最后两行包含 `panic!` 调用造成的错误信息。第一行显示了 panic 提供的信息并指明了源码中 panic 出现的位置:*src/main.rs:2:4* 表明这是 *src/main.rs* 文件的第二行第四个字符。
最后两行包含 `panic!` 调用造成的错误信息。第一行显示了 panic 提供的信息并指明了源码中 panic 出现的位置:*src/main.rs:2:5* 表明这是 *src/main.rs* 文件的第二行第五个字符。
在这个例子中,被指明的那一行是我们代码的一部分,而且查看这一行的话就会发现 `panic!` 宏的调用。在其他情况下,`panic!` 可能会出现在我们的代码调用的代码中。错误信息报告的文件名和行号可能指向别人代码中的 `panic!` 宏调用,而不是我们代码中最终导致 `panic!` 的那一行。可以使用 `panic!` 被调用的函数的 backtrace 来寻找(我们代码中出问题的地方。下面我们会详细介绍 backtrace 是什么。
在这个例子中,被指明的那一行是我们代码的一部分,而且查看这一行的话就会发现 `panic!` 宏的调用。在其他情况下,`panic!` 可能会出现在我们的代码调用的代码中。错误信息报告的文件名和行号可能指向别人代码中的 `panic!` 宏调用,而不是我们代码中最终导致 `panic!` 的那一行。我们可以使用 `panic!` 被调用的函数的 backtrace 来寻找代码中出问题的地方。下面我们会详细介绍 backtrace 是什么。
### 使用 `panic!` 的 backtrace
@ -65,10 +65,9 @@ fn main() {
```text
$ cargo run
Compiling panic v0.1.0 (file:///projects/panic)
Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
Finished dev [unoptimized + debuginfo] target(s) in 0.27s
Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
99', /checkout/src/liballoc/vec.rs:1555:10
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', libcore/slice/mod.rs:2448:10
note: Run with `RUST_BACKTRACE=1` for a backtrace.
```
@ -78,50 +77,60 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
```text
$ RUST_BACKTRACE=1 cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Finished dev [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/panic`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', /checkout/src/liballoc/vec.rs:1555:10
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', libcore/slice/mod.rs:2448:10
stack backtrace:
0: std::sys::imp::backtrace::tracing::imp::unwind_backtrace
at /checkout/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
1: std::sys_common::backtrace::_print
at /checkout/src/libstd/sys_common/backtrace.rs:71
0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
1: std::sys_common::backtrace::print
at libstd/sys_common/backtrace.rs:71
at libstd/sys_common/backtrace.rs:59
2: std::panicking::default_hook::{{closure}}
at /checkout/src/libstd/sys_common/backtrace.rs:60
at /checkout/src/libstd/panicking.rs:381
at libstd/panicking.rs:211
3: std::panicking::default_hook
at /checkout/src/libstd/panicking.rs:397
4: std::panicking::rust_panic_with_hook
at /checkout/src/libstd/panicking.rs:611
5: std::panicking::begin_panic
at /checkout/src/libstd/panicking.rs:572
6: std::panicking::begin_panic_fmt
at /checkout/src/libstd/panicking.rs:522
7: rust_begin_unwind
at /checkout/src/libstd/panicking.rs:498
8: core::panicking::panic_fmt
at /checkout/src/libcore/panicking.rs:71
9: core::panicking::panic_bounds_check
at /checkout/src/libcore/panicking.rs:58
10: <alloc::vec::Vec<T> as core::ops::index::Index<usize>>::index
at /checkout/src/liballoc/vec.rs:1555
11: panic::main
at libstd/panicking.rs:227
4: <std::panicking::begin_panic::PanicPayload<A> as core::panic::BoxMeUp>::get
at libstd/panicking.rs:476
5: std::panicking::continue_panic_fmt
at libstd/panicking.rs:390
6: std::panicking::try::do_call
at libstd/panicking.rs:325
7: core::ptr::drop_in_place
at libcore/panicking.rs:77
8: core::ptr::drop_in_place
at libcore/panicking.rs:59
9: <usize as core::slice::SliceIndex<[T]>>::index
at libcore/slice/mod.rs:2448
10: core::slice::<impl core::ops::index::Index<I> for [T]>::index
at libcore/slice/mod.rs:2316
11: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index
at liballoc/vec.rs:1653
12: panic::main
at src/main.rs:4
12: __rust_maybe_catch_panic
at /checkout/src/libpanic_unwind/lib.rs:99
13: std::rt::lang_start
at /checkout/src/libstd/panicking.rs:459
at /checkout/src/libstd/panic.rs:361
at /checkout/src/libstd/rt.rs:61
14: main
15: __libc_start_main
16: <unknown>
13: std::rt::lang_start::{{closure}}
at libstd/rt.rs:74
14: std::panicking::try::do_call
at libstd/rt.rs:59
at libstd/panicking.rs:310
15: macho_symbol_search
at libpanic_unwind/lib.rs:102
16: std::alloc::default_alloc_error_hook
at libstd/panicking.rs:289
at libstd/panic.rs:392
at libstd/rt.rs:58
17: std::rt::lang_start
at libstd/rt.rs:74
18: panic::main
```
<span class="caption">示例 9-2当设置 `RUST_BACKTRACE` 环境变量时 `panic!` 调用所生成的 backtrace 信息</span>
这里有大量的输出!你实际看到的输出可能因不同的操作系统和 Rust 版本而有所不同。为了获取带有这些信息的 backtrace必须启用 debug 标识。当不使用 `--release` 参数运行 cargo build 或 cargo run 时 debug 标识会默认启用,就像这里一样。
示例 9-2 的输出中backtrace 的 11 行指向了我们项目中造成问题的行:*src/main.rs* 的第 4 行。如果你不希望程序 panic第一个提到我们编写的代码行的位置是你应该开始调查的以便查明是什么值如何在这个地方引起了 panic。在示例 9-1 中,我们故意编写会 panic 的代码来演示如何使用 backtrace修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你的代码出现了 panic你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic以及应当如何处理才能避免这个问题。
示例 9-2 的输出中backtrace 的 12 行指向了我们项目中造成问题的行:*src/main.rs* 的第 4 行。如果你不希望程序 panic第一个提到我们编写的代码行的位置是你应该开始调查的以便查明是什么值如何在这个地方引起了 panic。在示例 9-1 中,我们故意编写会 panic 的代码来演示如何使用 backtrace修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你的代码出现了 panic你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic以及应当如何处理才能避免这个问题。
本章后面的小节 “panic! 还是不 panic!”会再次回到 `panic!` 会回到 `panic!` 并讲解何时应该何时不应该使用 `panic!` 来处理错误情况。接下来,我们来看看如何使用 `Result` 来从错误中恢复。
本章后面的小节 [“panic! 还是不 panic!”][to-panic-or-not-to-panic] 会再次回到 `panic!` 会回到 `panic!` 并讲解何时应该何时不应该使用 `panic!` 来处理错误情况。接下来,我们来看看如何使用 `Result` 来从错误中恢复。
[to-panic-or-not-to-panic]:
ch09-03-to-panic-or-not-to-panic.html#to-panic-or-not-to-panic

View File

@ -2,11 +2,11 @@
> [ch09-02-recoverable-errors-with-result.md](https://github.com/rust-lang/book/blob/master/src/ch09-02-recoverable-errors-with-result.md)
> <br>
> commit db53e2e3cdf77beac853df6f29db4b3b86ea598c
> commit aa339f78da31c330ede3f1b52b4bbfb62d7814cb
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反应的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而失败,此时我们可能想要创建这个文件而不是终止进程。
回忆一下第二章 “使用 `Result` 类型来处理潜在的错误” 部分中的那个 `Result` 枚举,它定义有如下两个成员,`Ok` 和 `Err`
回忆一下第二章 [“使用 `Result` 类型来处理潜在的错误”][handle_failure] 部分中的那个 `Result` 枚举,它定义有如下两个成员,`Ok` 和 `Err`
[handle_failure]: ch02-00-guessing-game-tutorial.html#handling-potential-failure-with-the-result-type
@ -35,7 +35,7 @@ fn main() {
如何知道 `File::open` 返回一个 `Result` 呢?我们可以查看 [标准库 API 文档](https://doc.rust-lang.org/std/index.html)<!-- ignore -->,或者可以直接问编译器!如果给 `f` 某个我们知道 **不是** 函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们 `f` 的类型 **应该** 是什么。让我们试试!我们知道 `File::open` 的返回值不是 `u32` 类型的,所以将 `let f` 语句改为如下:
```rust,ignore
```rust,ignore,does_not_compile
let f: u32 = File::open("hello.txt");
```
@ -72,13 +72,13 @@ fn main() {
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("There was a problem opening the file: {:?}", error)
panic!("Problem opening the file: {:?}", error)
},
};
}
```
<span class="caption">示例 9-4使用 `match` 表达式处理可能的 `Result` 成员</span>
<span class="caption">示例 9-4使用 `match` 表达式处理可能会返回`Result` 成员</span>
注意与 `Option` 枚举一样,`Result` 枚举和其成员也被导入到了 prelude 中,所以就不需要在 `match` 分支中的 `Ok``Err` 之前指定 `Result::`
@ -87,11 +87,11 @@ fn main() {
`match` 的另一个分支处理从 `File::open` 得到 `Err` 值的情况。在这种情况下,我们选择调用 `panic!` 宏。如果当前目录没有一个叫做 *hello.txt* 的文件,当运行这段代码时会看到如下来自 `panic!` 宏的输出:
```text
thread 'main' panicked at 'There was a problem opening the file: Error { repr:
thread 'main' panicked at 'Problem opening the file: Error { repr:
Os { code: 2, message: "No such file or directory" } }', src/main.rs:9:12
```
输出一如既往告诉了我们到底出了什么错。
一如既往输出告诉了我们到底出了什么错。
### 匹配不同的错误
@ -99,9 +99,6 @@ Os { code: 2, message: "No such file or directory" } }', src/main.rs:9:12
<span class="filename">文件名: src/main.rs</span>
<!-- ignore this test because otherwise it creates hello.txt which causes other
tests to fail lol -->
```rust,ignore
use std::fs::File;
use std::io::ErrorKind;
@ -114,9 +111,9 @@ fn main() {
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Tried to create file but there was a problem: {:?}", e),
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => panic!("There was a problem opening the file: {:?}", other_error),
other_error => panic!("Problem opening the file: {:?}", other_error),
},
};
}
@ -147,7 +144,7 @@ fn main() {
}
```
在阅读完第十三章后再回到这个例子,并查看标准库文档 `unwrap_or_else` 方法都做了什么操作。在处理错误时,还有很多这类技巧可以消除大量嵌套的 `match` 表达式。
虽然这段代码有着如示例 9-5 一样的行为,但并没有包好任何 `match` 表达式且更容易阅读。在阅读完第十三章后再回到这个例子,并查看标准库文档 `unwrap_or_else` 方法都做了什么操作。在处理错误时,还有很多这类方法可以消除大量嵌套的 `match` 表达式。
### 失败时 panic 的简写:`unwrap` 和 `expect`
@ -232,11 +229,11 @@ fn read_username_from_file() -> Result<String, io::Error> {
调用这个函数的代码最终会得到一个包含用户名的 `Ok` 值,或者一个包含 `io::Error``Err` 值。我们无从得知调用者会如何处理这些值。例如,如果他们得到了一个 `Err` 值,他们可能会选择 `panic!` 并使程序崩溃、使用一个默认的用户名或者从文件之外的地方寻找用户名。我们没有足够的信息知晓调用者具体会如何尝试,所以将所有的成功或失败信息向上传播,让他们选择合适的处理方法。
这种传播错误的模式在 Rust 是如此的常见,以至于有一个更简便的专用语法:`?`
这种传播错误的模式在 Rust 是如此的常见,以至于 Rust 提供了 `?` 问号运算符来使其更易于处理
### 传播错误的简写:`?`
### 传播错误的简写:`?` 运算符
示例 9-7 展示了一个 `read_username_from_file` 的实现,它实现了与示例 9-6 中的代码相同的功能,不过这个实现使用了问号运算符:
示例 9-7 展示了一个 `read_username_from_file` 的实现,它实现了与示例 9-6 中的代码相同的功能,不过这个实现使用了 `?` 运算符:
<span class="filename">文件名: src/main.rs</span>
@ -253,15 +250,15 @@ fn read_username_from_file() -> Result<String, io::Error> {
}
```
<span class="caption">示例 9-7一个使用 `?` 向调用者返回错误的函数</span>
<span class="caption">示例 9-7一个使用 `?` 运算符向调用者返回错误的函数</span>
`Result` 值之后的 `?` 被定义为与示例 9-6 中定义的处理 `Result` 值的 `match` 表达式有着完全相同的工作方式。如果 `Result` 的值是 `Ok`,这个表达式将会返回 `Ok` 中的值而程序将继续执行。如果值是 `Err``Err` 中的值将作为整个函数的返回值,就好像使用了 `return` 关键字一样,这样错误值就被传播给了调用者。
示例 9-6 中的 `match` 表达式与问号运算符所做的有一点不同:`?` 所使用的错误值被传递给了 `from` 函数,它定义于标准库的 `From` trait 中,其用来将错误从一种类型转换为另一种类型。当 `?` 调用 `from` 函数时,收到的错误类型被转换为定义为当前函数返回的错误类型。这在当一个函数返回一个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。只要每一个错误类型都实现了 `from` 函数来定义如将其转换为返回的错误类型,`?` 会自动处理这些转换。
示例 9-6 中的 `match` 表达式与问号运算符所做的有一点不同:`?` 运算符所使用的错误值被传递给了 `from` 函数,它定义于标准库的 `From` trait 中,其用来将错误从一种类型转换为另一种类型。当 `?` 运算符调用 `from` 函数时,收到的错误类型被转换为定义为当前函数返回的错误类型。这在当一个函数返回一个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。只要每一个错误类型都实现了 `from` 函数来定义如将其转换为返回的错误类型,`?` 运算符会自动处理这些转换。
在示例 9-7 的上下文中,`File::open` 调用结尾的 `?` 将会把 `Ok` 中的值返回给变量 `f`。如果出现了错误,`?` 会提早返回整个函数并将一些 `Err` 值传播给调用者。同理也适用于 `read_to_string` 调用结尾的 `?`
在示例 9-7 的上下文中,`File::open` 调用结尾的 `?` 将会把 `Ok` 中的值返回给变量 `f`。如果出现了错误,`?` 运算符会提早返回整个函数并将一些 `Err` 值传播给调用者。同理也适用于 `read_to_string` 调用结尾的 `?`
`?` 消除了大量样板代码并使得函数的实现更简单。我们甚至可以在 `?` 之后直接使用链式方法调用来进一步缩短代码,如示例 9-8 所示:
`?` 运算符消除了大量样板代码并使得函数的实现更简单。我们甚至可以在 `?` 之后直接使用链式方法调用来进一步缩短代码,如示例 9-8 所示:
<span class="filename">文件名: src/main.rs</span>
@ -300,13 +297,13 @@ fn read_username_from_file() -> Result<String, io::Error> {
将文件读取到一个字符串是相当常见的操作,所以 Rust 提供了名为 `fs::read_to_string` 的函数,它会打开文件、新建一个 `String`、读取文件的内容,并将内容放入 `String`,接着返回它。当然,这样做就没有展示所有这些错误处理的机会了,所以我们最初就选择了艰苦的道路。
### `?` 只能被用于返回 `Result` 的函数
### `?` 运算符可被用于返回 `Result` 的函数
`?` 只能被用于返回值类型为 `Result` 的函数,因为他被定义为与示例 9-6 中的 `match` 表达式有着完全相同的工作方式。`match` 的 `return Err(e)` 部分要求返回值类型是 `Result`,所以函数的返回值必须是 `Result` 才能与这个 `return` 相兼容。
`?` 运算符可被用于返回值类型为 `Result` 的函数,因为他被定义为与示例 9-6 中的 `match` 表达式有着完全相同的工作方式。`match` 的 `return Err(e)` 部分要求返回值类型是 `Result`,所以函数的返回值必须是 `Result` 才能与这个 `return` 相兼容。
让我们看看在 `main` 函数中使用 `?` 会发生什么,如果你还记得的话其返回值类型是`()`
让我们看看在 `main` 函数中使用 `?` 运算符会发生什么,如果你还记得的话其返回值类型是`()`
```rust,ignore
```rust,ignore,does_not_compile
use std::fs::File;
fn main() {
@ -317,19 +314,21 @@ fn main() {
当编译这些代码,会得到如下错误信息:
```text
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`)
error[E0277]: the `?` operator can only be used in a function that returns
`Result` or `Option` (or another type that implements `std::ops::Try`)
--> src/main.rs:4:13
|
4 | let f = File::open("hello.txt")?;
| ^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
| ^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a
function that returns `()`
|
= help: the trait `std::ops::Try` is not implemented for `()`
= note: required by `std::ops::Try::from_error`
```
错误指出只能在返回 `Result` 的函数中使用 `?`。在不返回 `Result` 的函数中,当调用其他返回 `Result` 的函数时,需要使用 `match``Result` 的方法之一来处理,而不能用 `?` 将潜在的错误传播给代码调用方
错误指出只能在返回 `Result` 或者其它实现了 `std::ops::Try` 的类型的函数中使用 `?` 运算符。当你期望在不返回 `Result` 的函数中调用其他返回 `Result` 的函数时使用 `?` 的话,有两种方法修复这个问题。一种技巧是将函数返回值类型修改为 `Result<T, E>`,如果没有其它限制组织你这么做的话。另一种技巧是通过合适的方法使用 `match``Result` 的方法之一来处理 `Result<T, E>`
不过 `main` 函数可以返回一个 `Result<T, E>`
`main` 函数是特殊的,其必须返回什么类型是有限制的。`main` 函数的一个有效的返回值是 `()`,同时出于方便,另一个有效的返回值是 `Result<T, E>`,如下所示
```rust,ignore
use std::error::Error;
@ -342,6 +341,8 @@ fn main() -> Result<(), Box<dyn Error>> {
}
```
`Box<dyn Error>` 被称为 “trait 对象”“trait object”第十七章会介绍。目前可以理解 `Box<dyn Error>` 为 “任何类型的错误”。
`Box<dyn Error>` 被称为 “trait 对象”“trait object”第十七章 [“为使用不同类型的值而设计的 trait 对象”][trait-objects] 部分介绍。目前可以理解 `Box<dyn Error>`使用 `?``main` 允许返回的 “任何类型的错误”。
现在我们讨论过了调用 `panic!` 或返回 `Result` 的细节,是时候回到他们各自适合哪些场景的话题了。
[trait-objects]: ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types

View File

@ -2,7 +2,7 @@
> [ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-03-to-panic-or-not-to-panic.md)
> <br>
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
> commit 76df60bccead5f3de96db23d97b69597cd8a2b82
那么,该如何决定何时应该 `panic!` 以及何时应该返回 `Result` 呢?如果代码 panic就没有恢复的可能。你可以选择对任何错误场景都调用 `panic!`,不管是否有可能恢复,不过这样就是你代替调用者决定了这是不可恢复的。选择返回 `Result` 值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为 `Err` 是不可恢复的,所以他们也可能会调用 `panic!` 并将可恢复的错误变成了不可恢复的错误。因此返回 `Result` 是定义可能会失败的函数的一个好的默认选择。
@ -44,6 +44,8 @@ let home: IpAddr = "127.0.0.1".parse().unwrap();
虽然在所有函数中都拥有许多错误检查是冗长而烦人的。幸运的是,可以利用 Rust 的类型系统(以及编译器的类型检查)为你进行很多检查。如果函数有一个特定类型的参数,可以在知晓编译器已经确保其拥有一个有效值的前提下进行你的代码逻辑。例如,如果你使用了一个不同于 `Option` 的类型,而且程序期望它是 **有值** 的并且不是 **空值**。你的代码无需处理 `Some``None` 这两种情况,它只会有一种情况就是绝对会有一个值。尝试向函数传递空值的代码甚至根本不能编译,所以你的函数在运行时没有必要判空。另外一个例子是使用像 `u32` 这样的无符号整型,也会确保它永远不为负。
### 创建自定义类型进行有效性验证
让我们使用 Rust 类型系统的思想来进一步确保值的有效性,并尝试创建一个自定义类型以进行验证。回忆一下第二章的猜猜看游戏,我们的代码要求用户猜测一个 1 到 100 之间的数字在将其与秘密数字做比较之前我们从未验证用户的猜测是位于这两个数字之间的我们只验证它是否为正。在这种情况下其影响并不是很严重“Too high” 或 “Too low” 的输出仍然是正确的。但是这是一个很好的引导用户得出有效猜测的辅助,例如当用户猜测一个超出范围的数字或者输入字母时采取不同的行为。
一种实现方式是将猜测解析成 `i32` 而不仅仅是 `u32`,来默许输入负数,接着检查数字是否在范围内: