mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
commit
b53f198f6c
@ -4,7 +4,7 @@
|
||||
> <br>
|
||||
> commit b374e75f1d7b743c84a6bb1ef72579a6588bcb8a
|
||||
|
||||
Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及它们的作用。`match` 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
|
||||
Rust 有一个叫做 `match` 的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较,并根据相匹配的模式执行相应代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及它们的作用。`match` 的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
|
||||
|
||||
可以把 `match` 表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会通过 `match` 的每一个模式,并且在遇到第一个 “符合” 的模式时,值会进入相关联的代码块并在执行中被使用。
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
称作 `String` 的类型是由标准库提供的,而没有写进核心语言部分,它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 和字符串 slice `&str` 类型,而不仅仅是其中之一。虽然本部分内容大多是关于 `String` 的,不过这两个类型在 Rust 标准库中都被广泛使用,`String` 和字符串 slice 都是 UTF-8 编码的。
|
||||
|
||||
Rust 标准库中还包含一系列其他字符串类型,比如 `OsString`、`OsStr`、`CString` 和 `CStr`。相关库 crate 甚至会提供更多储存字符串数据的选择。看到这些由 `String` 或是 `Str` 结尾的名字了吗?这对应着它们提供的所有权和可借用的字符串变体,就像是你之前看到的 `String` 和 `str`。举例而言,这些字符串类型能够以不同的编码或内存表现形式上以不同的形式存储文本内容。本章将不会讨论其他这些字符串类型,查看 API 文档来更多的了解如何使用它们以及各自适合的场景。
|
||||
Rust 标准库中还包含一系列其他字符串类型,比如 `OsString`、`OsStr`、`CString` 和 `CStr`。相关库 crate 甚至会提供更多储存字符串数据的选择。看到这些由 `String` 或是 `Str` 结尾的名字了吗?这对应着它们提供的所有权和可借用的字符串变体,就像是你之前看到的 `String` 和 `str`。举例而言,这些字符串类型能够以不同的编码,或者内存表现形式上以不同的形式,来存储文本内容。本章将不会讨论其他这些字符串类型,更多有关如何使用它们以及各自适合的场景,请参见其API文档。
|
||||
|
||||
### 新建字符串
|
||||
|
||||
@ -49,7 +49,7 @@ let s = String::from("initial contents");
|
||||
|
||||
<span class="caption">示例 8-13:使用 `String::from` 函数从字符串字面值创建 `String`</span>
|
||||
|
||||
因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。它们有些可能显得有些多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是风格问题了。
|
||||
因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。其中一些可能看起来多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是风格问题了。
|
||||
|
||||
记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据,如示例 8-14 所示。
|
||||
|
||||
@ -73,7 +73,7 @@ let hello = String::from("Hola");
|
||||
|
||||
### 更新字符串
|
||||
|
||||
`String` 的大小可以增长其内容也可以改变,就像可以放入更多数据来改变 `Vec` 的内容一样。另外,可以方便的使用 `+` 运算符或 `format!` 宏来拼接 `String` 值。
|
||||
`String` 的大小可以增加,其内容也可以改变,就像可以放入更多数据来改变 `Vec` 的内容一样。另外,可以方便的使用 `+` 运算符或 `format!` 宏来拼接 `String` 值。
|
||||
|
||||
#### 使用 `push_str` 和 `push` 附加字符串
|
||||
|
||||
@ -86,7 +86,7 @@ s.push_str("bar");
|
||||
|
||||
<span class="caption">示例 8-15:使用 `push_str` 方法向 `String` 附加字符串 slice</span>
|
||||
|
||||
执行这两行代码之后 `s` 将会包含 `foobar`。`push_str` 方法获取字符串 slice,因为我们并不需要获取参数的所有权。例如,示例 8-16 展示了如果将 `s2` 的内容附加到 `s1` 中后自身不能被使用就糟糕了。
|
||||
执行这两行代码之后,`s` 将会包含 `foobar`。`push_str` 方法采用字符串 slice,因为我们并不需要获取参数的所有权。例如,示例 8-16 展示了如果将 `s2` 的内容附加到 `s1` 之后,自身不能被使用就糟糕了。
|
||||
|
||||
```rust
|
||||
let mut s1 = String::from("foo");
|
||||
@ -122,7 +122,7 @@ let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
|
||||
|
||||
<span class="caption">示例 8-18:使用 `+` 运算符将两个 `String` 值合并到一个新的 `String` 值中</span>
|
||||
|
||||
执行完这些代码之后字符串 `s3` 将会包含 `Hello, world!`。`s1` 在相加后不再有效的原因,和使用 `s2` 的引用的原因与使用 `+` 运算符时调用的方法签名有关,这个函数签名看起来像这样:
|
||||
执行完这些代码之后,字符串 `s3` 将会包含 `Hello, world!`。`s1` 在相加后不再有效的原因,和使用 `s2` 的引用的原因,与使用 `+` 运算符时调用的函数签名有关。`+` 运算符使用了 `add` 函数,这个函数签名看起来像这样:
|
||||
|
||||
```rust,ignore
|
||||
fn add(self, s: &str) -> String {
|
||||
@ -134,7 +134,7 @@ fn add(self, s: &str) -> String {
|
||||
|
||||
之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转**(*coerced*)成 `&str`。当`add`函数被调用时,Rust 使用了一个被称为 **解引用强制多态**(*deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]`。第十五章会更深入的讨论解引用强制多态。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`。
|
||||
|
||||
其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有** 使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝不过实际上并没有:这个实现比拷贝要更高效。
|
||||
其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有** 使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝,不过实际上并没有:这个实现比拷贝要更高效。
|
||||
|
||||
如果想要级联多个字符串,`+` 的行为就显得笨重了:
|
||||
|
||||
@ -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 根本不会编译这些代码,并在开发过程中及早杜绝了误会的发生。
|
||||
|
||||
#### 字节、标量值和字形簇!天呐!
|
||||
|
||||
@ -230,11 +230,11 @@ let answer = &hello[0];
|
||||
|
||||
Rust 提供了多种不同的方式来解释计算机储存的原始字符串数据,这样程序就可以选择它需要的表现方式,而无所谓是何种人类语言。
|
||||
|
||||
最后一个 Rust 不允许使用索引获取 `String` 字符的原因是索引操作预期总是需要常数时间 (O(1))。但是对于 `String` 不可能保证这样的性能,因为 Rust 不得不检查从字符串的开头到索引位置的内容来确定这里有多少有效的字符。
|
||||
最后一个 Rust 不允许使用索引获取 `String` 字符的原因是,索引操作预期总是需要常数时间 (O(1))。但是对于 `String` 不可能保证这样的性能,因为 Rust 必须从开头到索引位置遍历来确定有多少有效的字符。
|
||||
|
||||
### 字符串 slice
|
||||
|
||||
索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时 Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用 `[]` 和单个值的索引,可以使用 `[]` 和一个 range 来创建含特定字节的字符串 slice:
|
||||
索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时,Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用 `[]` 和单个值的索引,可以使用 `[]` 和一个 range 来创建含特定字节的字符串 slice:
|
||||
|
||||
```rust
|
||||
let hello = "Здравствуйте";
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能得到他们的得分。
|
||||
|
||||
本章我们会介绍哈希 map 的基本 API,不过还有更多吸引人的功能隐藏于标准库中 `HashMap<K, V>` 定义的函数中。一如既往请查看标准库文档来了解更多信息。
|
||||
本章我们会介绍哈希 map 的基本 API,不过还有更多吸引人的功能隐藏于标准库在 `HashMap<K, V>` 上定义的函数中。一如既往请查看标准库文档来了解更多信息。
|
||||
|
||||
### 新建一个哈希 map
|
||||
|
||||
@ -195,4 +195,4 @@ vector、字符串和哈希 map 会在你的程序需要储存、访问和修改
|
||||
我们已经开始接触可能会有失败操作的复杂程序了,这也意味着接下来是一个了解错误处理的绝佳时机!
|
||||
|
||||
[validating-references-with-lifetimes]:
|
||||
ch10-03-lifetime-syntax.html#validating-references-with-lifetimes
|
||||
ch10-03-lifetime-syntax.html#validating-references-with-lifetimes
|
||||
|
@ -4,7 +4,7 @@
|
||||
> <br>
|
||||
> commit 1fedfc4b96c2017f64ecfcf41a0a07e2e815f24f
|
||||
|
||||
Rust 对可靠性的执着也延伸到了错误处理。错误对于软件来说是不可避免的,所以 Rust 有很多特性来处理出现错误的情况。在很多情况下,Rust 要求你承认出错的可能性并在编译代码之前就采取行动。这些要求使得程序更为健壮,它们确保了你会在将代码部署到生产环境之前就发现错误并正确地处理它们!
|
||||
Rust 对可靠性的执着也延伸到了错误处理。错误对于软件来说是不可避免的,所以 Rust 有很多特性来处理出现错误的情况。在很多情况下,Rust 要求你承认出错的可能性,并在编译代码之前就采取行动。这些要求使得程序更为健壮,它们确保了你会在将代码部署到生产环境之前就发现错误并正确地处理它们!
|
||||
|
||||
Rust 将错误组合成两个主要类别:**可恢复错误**(*recoverable*)和 **不可恢复错误**(*unrecoverable*)。可恢复错误通常代表向用户报告错误和重试操作是合理的情况,比如未找到文件。不可恢复错误通常是 bug 的同义词,比如尝试访问超过数组结尾的位置。
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
> <br>
|
||||
> commit 426f3e4ec17e539ae9905ba559411169d303a031
|
||||
|
||||
突然有一天,代码出问题了,而你对此束手无策。对于这种情况,Rust 有 `panic!`宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。
|
||||
突然有一天,代码出问题了,而你对此束手无策。对于这种情况,Rust 有 `panic!`宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug,而且程序员并不清楚该如何处理它。
|
||||
|
||||
> ### 对应 panic 时的栈展开或终止
|
||||
>
|
||||
@ -38,7 +38,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||
|
||||
最后两行包含 `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
|
||||
|
||||
@ -73,7 +73,7 @@ note: Run with `RUST_BACKTRACE=1` for a backtrace.
|
||||
|
||||
这指向了一个不是我们编写的文件,*vec.rs*。这是标准库中 `Vec<T>` 的实现。这是当对 vector `v` 使用 `[]` 时 *vec.rs* 中会执行的代码,也是真正出现 `panic!` 的地方。
|
||||
|
||||
接下来的几行提醒我们可以设置 `RUST_BACKTRACE` 环境变量来得到一个 backtrace *backtrace* 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。让我们将 `RUST_BACKTRACE` 环境变量设置为任何不是 0 的值来获取 backtrace 看看。示例 9-2 展示了与你看到类似的输出:
|
||||
接下来的几行提醒我们可以设置 `RUST_BACKTRACE` 环境变量来得到一个 backtrace。*backtrace* 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码所调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。让我们将 `RUST_BACKTRACE` 环境变量设置为任何不是 0 的值来获取 backtrace 看看。示例 9-2 展示了与你看到类似的输出:
|
||||
|
||||
```text
|
||||
$ RUST_BACKTRACE=1 cargo run
|
||||
@ -130,7 +130,7 @@ stack backtrace:
|
||||
|
||||
示例 9-2 的输出中,backtrace 的 12 行指向了我们项目中造成问题的行:*src/main.rs* 的第 4 行。如果你不希望程序 panic,第一个提到我们编写的代码行的位置是你应该开始调查的,以便查明是什么值如何在这个地方引起了 panic。在示例 9-1 中,我们故意编写会 panic 的代码来演示如何使用 backtrace,修复这个 panic 的方法就是不要尝试在一个只包含三个项的 vector 中请求索引是 100 的元素。当将来你的代码出现了 panic,你需要搞清楚在这特定的场景下代码中执行了什么操作和什么值导致了 panic,以及应当如何处理才能避免这个问题。
|
||||
|
||||
本章后面的小节 [“panic! 还是不 panic!”][to-panic-or-not-to-panic] 会再次回到 `panic!` 会回到 `panic!` 并讲解何时应该何时不应该使用 `panic!` 来处理错误情况。接下来,我们来看看如何使用 `Result` 来从错误中恢复。
|
||||
本章后面的小节 [“panic! 还是不 panic!”][to-panic-or-not-to-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
|
||||
ch09-03-to-panic-or-not-to-panic.html#to-panic-or-not-to-panic
|
||||
|
@ -4,7 +4,7 @@
|
||||
> <br>
|
||||
> commit aa339f78da31c330ede3f1b52b4bbfb62d7814cb
|
||||
|
||||
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反应的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而失败,此时我们可能想要创建这个文件而不是终止进程。
|
||||
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反应的原因失败。例如,如果因为打开一个并不存在的文件而失败,此时我们可能想要创建这个文件,而不是终止进程。
|
||||
|
||||
回忆一下第二章 [“使用 `Result` 类型来处理潜在的错误”][handle_failure] 部分中的那个 `Result` 枚举,它定义有如下两个成员,`Ok` 和 `Err`:
|
||||
|
||||
@ -91,7 +91,7 @@ thread 'main' panicked at 'Problem opening the file: Error { repr:
|
||||
Os { code: 2, message: "No such file or directory" } }', src/main.rs:9:12
|
||||
```
|
||||
|
||||
一如既往输出告诉了我们到底出了什么错。
|
||||
一如既往,此输出准确地告诉了我们到底出了什么错。
|
||||
|
||||
### 匹配不同的错误
|
||||
|
||||
@ -123,7 +123,7 @@ fn main() {
|
||||
|
||||
`File::open` 返回的 `Err` 成员中的值类型 `io::Error`,它是一个标准库中提供的结构体。这个结构体有一个返回 `io::ErrorKind` 值的 `kind` 方法可供调用。`io::ErrorKind` 是一个标准库提供的枚举,它的成员对应 `io` 操作可能导致的不同错误类型。我们感兴趣的成员是 `ErrorKind::NotFound`,它代表尝试打开的文件并不存在。所以 `match` 的 `f` 匹配,不过对于 `error.kind()` 还有一个内部 `match`。
|
||||
|
||||
我们希望在匹配守卫中检查的条件是 `error.kind()` 的返回值是 `ErrorKind`的 `NotFound` 成员。如果是,则尝试通过 `File::create` 创建文件。然而因为 `File::create` 也可能会失败,还需要增加一个内部 `match` 语句。当文件不能被打开,会打印出一个不同的错误信息。外部 `match` 的最后一个分支保持不变这样对任何除了文件不存在的错误会使程序 panic。
|
||||
我们希望在匹配守卫中检查的条件是 `error.kind()` 的返回值是 `ErrorKind`的 `NotFound` 成员。如果是,则尝试通过 `File::create` 创建文件。然而因为 `File::create` 也可能会失败,还需要增加一个内部 `match` 语句。当文件不能被打开,会打印出一个不同的错误信息。外部 `match` 的最后一个分支保持不变,这样对任何除了文件不存在的错误会使程序 panic。
|
||||
|
||||
这里有好多 `match`!`match` 确实很强大,不过也非常的基础。第十三章我们会介绍闭包(closure)。`Result<T, E>` 有很多接受闭包的方法,并采用 `match` 表达式实现。一个更老练的 Rustacean 可能会这么写:
|
||||
|
||||
@ -345,4 +345,4 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
|
||||
现在我们讨论过了调用 `panic!` 或返回 `Result` 的细节,是时候回到他们各自适合哪些场景的话题了。
|
||||
|
||||
[trait-objects]: ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types
|
||||
[trait-objects]: ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types
|
||||
|
Loading…
Reference in New Issue
Block a user