mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2024-11-09 08:51:18 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
0b885182d4
@ -1,5 +1,7 @@
|
||||
# Rust 程序设计语言(第二版) 简体中文版
|
||||
|
||||
还在施工中:正在翻译第十六章第二部分
|
||||
还在施工中:目前翻译到第十六章,正在更新第十二章
|
||||
|
||||
目前正在解决代码排版问题:已检查到第五章
|
||||
目前官方进度:[第十七章](https://github.com/rust-lang/book/projects/1)(18~20 章还在编写当中)
|
||||
|
||||
GitBook 代码排版已大体解决,已不影响阅读
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
123
docs/ch17-00-oop.html
Normal file
123
docs/ch17-00-oop.html
Normal file
File diff suppressed because one or more lines are too long
180
docs/ch17-01-what-is-oo.html
Normal file
180
docs/ch17-01-what-is-oo.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2899
docs/print.html
2899
docs/print.html
File diff suppressed because one or more lines are too long
@ -1,5 +1,7 @@
|
||||
# Rust 程序设计语言(第二版) 简体中文版
|
||||
|
||||
还在施工中:正在翻译第十六章第二部分
|
||||
还在施工中:目前翻译到第十六章,正在更新第十二章
|
||||
|
||||
目前正在解决代码排版问题:已检查到第五章
|
||||
目前官方进度:[第十七章](https://github.com/rust-lang/book/projects/1)(18~20 章还在编写当中)
|
||||
|
||||
GitBook 代码排版已大体解决,已不影响阅读
|
@ -91,3 +91,8 @@
|
||||
- [消息传递](ch16-02-message-passing.md)
|
||||
- [共享状态](ch16-03-shared-state.md)
|
||||
- [可扩展的并发:`Sync`和`Send`](ch16-04-extensible-concurrency-sync-and-send.md)
|
||||
|
||||
- [面向对象](ch17-00-oop.md)
|
||||
- [什么是面向对象](ch17-01-what-is-oo.md)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch01-00-introduction.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch01-00-introduction.md)
|
||||
> <br>
|
||||
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
|
||||
> commit 62f78bb3f7c222b574ff547d0161c2533691f9b4
|
||||
|
||||
欢迎阅读“Rust 程序设计语言”,一本关于 Rust 的介绍性书籍。Rust 是一个着用于安全、速度和并发的编程语言。它的设计不仅可以使程序获得性能和对底层语言的控制,并且能够享受高级语言强大的抽象能力。这些特性使得 Rust 适合那些有类似 C 语言经验并正在寻找一个更安全的替代者的程序员,同时也适合那些来自类似 Python 语言背景,正在探索在不牺牲表现力的情况下编写更好性能代码的开发者。
|
||||
|
||||
@ -15,9 +15,10 @@ registry site),[crates.io]!我们期待看到**你**使用 Rust 进行创
|
||||
|
||||
## 为本书做出贡献
|
||||
|
||||
本书是开源的。如果你发现任何错误,请不要犹豫,[在 GitHub 上][on GitHub]发起 issue 或提交 pull request。
|
||||
本书是开源的。如果你发现任何错误,请不要犹豫,[在 GitHub 上][on GitHub]发起 issue 或提交 pull request。请查看[CONTRIBUTING.md]获取更多信息。
|
||||
|
||||
[on GitHub]: https://github.com/rust-lang/book
|
||||
[CONTRIBUTING.md]: https://github.com/rust-lang/book/blob/master/CONTRIBUTING.md
|
||||
|
||||
> 译者注:这是本译本的 [GitHub 仓库][trpl-zh-cn],同样欢迎 Issue 和 PR :)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch01-01-installation.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch01-01-installation.md)
|
||||
> <br>
|
||||
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
|
||||
> commit c1b95a18dbcbb06aadf07c03759f27d88ccf62cf
|
||||
|
||||
使用 Rust 的第一步是安装。你需要网络连接来执行本章的命令,因为我们要从网上下载 Rust。
|
||||
|
||||
@ -34,6 +34,15 @@ Rust is installed now. Great!
|
||||
|
||||
如果有理由倾向于不使用 rustup.rs,请查看[Rust 安装页面](https://www.rust-lang.org/install.html)获取其他选择。
|
||||
|
||||
|
||||
### 更新
|
||||
|
||||
一旦安装完 Rust,更新到最新版本是简单的。在 shell 中运行更新脚本:
|
||||
|
||||
```
|
||||
$ rustup update
|
||||
```
|
||||
|
||||
### 卸载
|
||||
|
||||
卸载 Rust 同安装一样简单。在 shell 中运行卸载脚本
|
||||
|
@ -261,4 +261,4 @@ help: consider removing this semicolon:
|
||||
| ^
|
||||
```
|
||||
|
||||
主要的错误信息,“mismatched types,”(类型不匹配),揭示了代码的核心问题。函数`plus_one`的定义说明它要返回一个`i32`,不过语句并不返回一个值,这由那个空元组`()`表明。因此,这个函数返回了空元组(),这与函数定义相矛盾并导致一个错误。在输出中,Rust 提供了一个可能会对修正问题有帮助的信息:它建议去掉分号,这会修复这个错误。
|
||||
主要的错误信息,“mismatched types,”(类型不匹配),揭示了代码的核心问题。函数`plus_one`的定义说明它要返回一个`i32`,不过语句并不返回一个值,这由那个空元组`()`表明。因此,这个函数返回了空元组`()`(译者注:原文说此函数没有返回任何值,可能有误),这与函数定义相矛盾并导致一个错误。在输出中,Rust 提供了一个可能会对修正问题有帮助的信息:它建议去掉分号,这会修复这个错误。
|
@ -305,7 +305,6 @@ the value is: 50
|
||||
|
||||
可以使用`for`循环来对一个集合的每个元素执行一些代码,来作为一个更有效率替代。`for`循环看起来像这样:
|
||||
|
||||
<figure>
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
@ -318,12 +317,8 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 3-6: Looping through each element of a collection using a `for` loop
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 3-6: Looping through each element of a collection
|
||||
using a `for` loop</span>
|
||||
|
||||
当运行这段代码,将看到与列表 3-5 一样的输出。更为重要的是,我们增强了代码安全性并消除了出现可能会导致超出数组的结尾或遍历长度不够而缺少一些元素这类 bug 机会。
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
> [ch04-02-references-and-borrowing.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch04-02-references-and-borrowing.md)
|
||||
> <br>
|
||||
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
|
||||
> commit 5e0546f53cce14b126527d9ba6d1b8eb212b4f3d
|
||||
|
||||
在上一部分的结尾处的使用元组的代码是有问题的,我们需要将`String`返回给调用者函数这样就可以在调用`calculate_length`后仍然可以使用`String`了,因为`String`先被移动到了`calculate_length`。
|
||||
|
||||
@ -243,7 +243,7 @@ fn dangle() -> &String { // dangle returns a reference to a String
|
||||
|
||||
因为`s`是在`dangle`创建的,当`dangle`的代码执行完毕后,`s`将被释放。不过我们尝试返回一个它的引用。这意味着这个引用会指向一个无效的`String`!这可不好。Rust 不会允许我们这么做的。
|
||||
|
||||
正确的代码是直接返回`String`:
|
||||
这里的解决方法是直接返回`String`:
|
||||
|
||||
```rust
|
||||
fn no_dangle() -> String {
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 结构体
|
||||
|
||||
> [ch05-00-structs.md](https://github.com/rust-lang/book/blob/master/src/ch05-00-structs.md)
|
||||
> [ch05-00-structs.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-00-structs.md)
|
||||
> <br>
|
||||
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
## 方法语法
|
||||
|
||||
> [ch05-01-method-syntax.md](https://github.com/rust-lang/book/blob/master/src/ch05-01-method-syntax.md)
|
||||
> [ch05-01-method-syntax.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch05-01-method-syntax.md)
|
||||
> <br>
|
||||
> commit 8c1c1a55d5c0f9bc3c866ee79b267df9dc5c04e2
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# 枚举和模式匹配
|
||||
|
||||
> [ch06-00-enums.md](https://github.com/rust-lang/book/blob/master/src/ch06-00-enums.md)
|
||||
> [ch06-00-enums.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-00-enums.md)
|
||||
> <br>
|
||||
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
# 定义枚举
|
||||
|
||||
> [ch06-01-defining-an-enum.md](https://github.com/rust-lang/book/blob/master/src/ch06-01-defining-an-enum.md)
|
||||
> [ch06-01-defining-an-enum.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-01-defining-an-enum.md)
|
||||
> <br>
|
||||
> commit e6d6caab41471f7115a621029bd428a812c5260e
|
||||
|
||||
让我们通过一用代码来表现的场景,来看看为什么这里枚举是有用的而且比结构体更合适。比如我们要处理 IP 地。目前被广泛使用的两个主要 IP 标准:IPv4(version four)和 IPv6(version six)。这是我们的程序只可能会遇到两种 IP 地址:我们可以**枚举**出所有可能的值,这也正是它名字的由来。
|
||||
让我们通过一用代码来表现的场景,来看看为什么这里枚举是有用的而且比结构体更合适。比如我们要处理 IP 地。目前被广泛使用的两个主要 IP 标准:IPv4(version four)和 IPv6(version six)。这是我们的程序只可能会遇到两种 IP 地址:所以可以**枚举**出所有可能的值,这也正是它名字的由来。
|
||||
|
||||
任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的而不能两者都是。IP 地址的这个特性使得枚举数据结构非常适合这个场景,因为枚举值尽可能是其一个成员。IPv4 和 IPv6 从根本上讲都是 IP 地址,所以当代码在处理申请任何类型的 IP 地址的场景时应该把他们当作相同的类型。
|
||||
任何一个 IP 地址要么是 IPv4 的要么是 IPv6 的而不能两者都是。IP 地址的这个特性使得枚举数据结构非常适合这个场景,因为枚举值只可能是其中一个成员。IPv4 和 IPv6 从根本上讲仍是 IP 地址,所以当代码在处理申请任何类型的 IP 地址的场景时应该把他们当作相同的类型。
|
||||
|
||||
可以通过在代码中定义一个`IpAddrKind`枚举来表现这个概念并列出可能的 IP 地址类型,`V4`和`V6`。这被称为枚举的**成员**(*variants*):
|
||||
|
||||
@ -58,9 +58,7 @@ route(IpAddrKind::V4);
|
||||
route(IpAddrKind::V6);
|
||||
```
|
||||
|
||||
使用枚举甚至还有更多优势。进一步考虑一下我们的 IP 地址类型,目前没有一个储存实际 IP 地址**数据**的方法;只知道它是什么**类型**的。考虑到已经在第五章学习过结构体了,你可以想如列表 6-1 那样修改这个问题:
|
||||
|
||||
<figure>
|
||||
使用枚举甚至还有更多优势。进一步考虑一下我们的 IP 地址类型,目前没有一个储存实际 IP 地址**数据**的方法;只知道它是什么**类型**的。考虑到已经在第五章学习过结构体了,你可以像列表 6-1 那样修改这个问题:
|
||||
|
||||
```rust
|
||||
enum IpAddrKind {
|
||||
@ -84,13 +82,8 @@ let loopback = IpAddr {
|
||||
};
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 6-1: Storing the data and `IpAddrKind` variant of an IP address using a
|
||||
`struct`
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 6-1: Storing the data and `IpAddrKind` variant of
|
||||
an IP address using a `struct`</span>
|
||||
|
||||
这里我们定义了一个有两个字段的结构体`IpAddr`:`kind`字段是`IpAddrKind`(之前定义的枚举)类型的而`address`字段是`String`类型的。这里有两个结构体的实例。第一个,`home`,它的`kind`的值是`IpAddrKind::V4`与之相关联的地址数据是`127.0.0.1`。第二个实例,`loopback`,`kind`的值是`IpAddrKind`的另一个成员,`V6`,关联的地址是`::1`。我们使用了要给结构体来将`kind`和`address`打包在一起,现在枚举成员就与值相关联了。
|
||||
|
||||
@ -124,7 +117,7 @@ let loopback = IpAddr::V6(String::from("::1"));
|
||||
|
||||
这些代码展示了使用枚举来储存两种不同 IP 地址的几种可能的选择。然而,事实证明储存和编码 IP 地址实在是太常见了[以致标准库提供了一个可供使用的定义!][IpAddr]<!-- ignore -->让我们看看标准库如何定义`IpAddr`的:它正有着跟我们定义和使用的一样的枚举和成员,不过它将成员中的地址数据嵌入到了两个不同形式的结构体中,他们对不同的成员的定义是不同的:
|
||||
|
||||
[IpAddr]: ../std/net/enum.IpAddr.html
|
||||
[IpAddr]: https://doc.rust-lang.org/std/net/enum.IpAddr.html
|
||||
|
||||
```rust
|
||||
struct Ipv4Addr {
|
||||
@ -141,14 +134,12 @@ enum IpAddr {
|
||||
}
|
||||
```
|
||||
|
||||
这些代码展示了可以将任意类型的数据放入枚举成员中:例如字符串、数字类型或者结构体。甚至可以包含另一个枚举!另外,标准库中的类型通常并不比你可能设想出来的要复杂多少。
|
||||
这些代码展示了可以将任意类型的数据放入枚举成员中:例如字符串、数字类型或者结构体。甚至可以包含另一个枚举!另外,标准库中的类型通常并不比你设想出来的要复杂多少。
|
||||
|
||||
注意虽然标准库中包含一个`IpAddr`的定义,仍然可以创建和使用我们自己的定义而不会有冲突,因为我们并没有将标准库中的定义引入作用域。第七章会讲到如何导入类型。
|
||||
|
||||
来看看列表 6-2 中的另一个枚举的例子:它的成员中内嵌了多种多样的类型:
|
||||
|
||||
<figure>
|
||||
|
||||
```rust
|
||||
enum Message {
|
||||
Quit,
|
||||
@ -158,13 +149,8 @@ enum Message {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 6-2: A `Message` enum whose variants each store different amounts and
|
||||
types of values
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 6-2: A `Message` enum whose variants each store
|
||||
different amounts and types of values</span>
|
||||
|
||||
这个枚举有四个含有不同类型的成员:
|
||||
|
||||
@ -173,7 +159,7 @@ types of values
|
||||
* `Write`包含单独一个`String`。
|
||||
* `ChangeColor`包含三个`i32`。
|
||||
|
||||
定义一个像列表 6-2 中的枚举类似于定义不同类型的结构体,除了枚举不使用`struct`关键字而且所有成员都被组合在一起位于`Message`下。如下这些结构体可以包含与之前枚举成员中相同的数据:
|
||||
定义一个像列表 6-2 中的枚举类似于定义不同类型的结构体,除了枚举不使用`struct`关键字而且所有成员都被组合在一起位于`Message`下之外。如下这些结构体可以包含与之前枚举成员中相同的数据:
|
||||
|
||||
|
||||
```rust
|
||||
@ -216,19 +202,28 @@ m.call();
|
||||
|
||||
在之前的部分,我们看到了`IpAddr`枚举如何利用 Rust 的类型系统编码更多信息而不单单是程序中的数据。这一部分探索一个`Option`的案例分析,它是标准库定义的另一个枚举。`Option`类型应用广泛因为它编码了一个非常普遍的场景,就是一个值可能是某个值或者什么都不是。从类型系统的角度来表达这个概念就意味着编译器需要检查是否处理了所有应该处理的情况,这样就可以避免在其他编程语言中非常常见的 bug。
|
||||
|
||||
编程语言的设计经常从其包含功能的角度考虑问题,但是从不包含的功能的角度思考也很重要。Rust 并没有很多其他语言中有的空值功能。**空值**(*Null* )是一个值它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。
|
||||
编程语言的设计经常从其包含功能的角度考虑问题,但是从其所没有的功能的角度思考也很重要。Rust 并没有很多其他语言中有的空值功能。**空值**(*Null* )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。
|
||||
|
||||
在“Null References: The Billion Dollar Mistake”中,Tony Hoare,null 的发明者,曾经说到:
|
||||
|
||||
> I call it my billion-dollar mistake. At that time, I was designing the first
|
||||
> comprehensive type system for references in an object-oriented language. My
|
||||
> goal was to ensure that all use of references should be absolutely safe, with
|
||||
> checking performed automatically by the compiler. But I couldn't resist the
|
||||
> temptation to put in a null reference, simply because it was so easy to
|
||||
> implement. This has led to innumerable errors, vulnerabilities, and system
|
||||
> crashes, which have probably caused a billion dollars of pain and damage in
|
||||
> the last forty years.
|
||||
>
|
||||
> 我称之为我万亿美元的错误。当时,我在在一个面向对象语言设计第一个综合性的面向引用的类型系统。我的目标是通过编译器的自动检查来保证所有引用的应有都应该是绝对安全的。不过我未能抗拒引入一个空引用的诱惑,仅仅是因为它是这么的容易实现。这引发了无数错误、漏洞和系统崩溃,在之后的四十多年中造成了数以万计美元的苦痛和伤害。
|
||||
|
||||
空值的问题在于当你尝试像一个非空值那样使用一个空值,会出现某种形式的错误。因为空和非空的属性是无处不在的,非常容易出现这类错误。
|
||||
|
||||
然而,空值尝试表达的概念仍然是有意义的:空值是一个因为某种原因目前无效或缺失的值。
|
||||
|
||||
问题不在于实际的概念而在于具体的实现。为此,Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是`Option<T>`,而且它[定义于标准库中][option]<!-- ignore -->,如下:
|
||||
问题不在于具体的概念而在于特定的实现。为此,Rust 并没有空值,不过它确实拥有一个可以编码存在或不存在概念的枚举。这个枚举是`Option<T>`,而且它[定义于标准库中][option]<!-- ignore -->,如下:
|
||||
|
||||
[option]: ../std/option/enum.Option.html
|
||||
[option]: https://doc.rust-lang.org/std/option/enum.Option.html
|
||||
|
||||
```rust
|
||||
enum Option<T> {
|
||||
@ -276,12 +271,12 @@ not satisfied
|
||||
|
||||
哇哦!事实上,错误信息意味着 Rust 不知道该如何将`Option<i8>`与`i8`相加。当在 Rust 中拥有一个像`i8`这样类型的值时,编译器确保它总是有一个有效的值。我们可以自信使用而无需判空。只有当使用`Option<i8>`(或者任何用到的类型)是需要担心可能没有一个值,而编译器会确保我们在使用值之前处理为空的情况。
|
||||
|
||||
换句话说,在对`Option<T>`进行`T`的运算之前必须转为`T`。通常这能帮助我们捕获空值最常见的问题之一:假设某值不为空但实际上为空。
|
||||
换句话说,在对`Option<T>`进行`T`的运算之前必须转为`T`。通常这能帮助我们捕获空值最常见的问题之一:假设某值不为空但实际上为空的情况。
|
||||
|
||||
无需担心错过非空值的假设(和处理)让我们对代码更加有信心,为了拥有一个可能为空的值,必须显式的将其放入对应类型的`Option<T>`中。接着,当使用这个值时,必须明确的处理值为空的情况。任何地方一个值不是`Option<T>`类型的话,**可以**安全的假设它的值不为空。这是 Rust 的一个有意为之的设计选择,来限制空值的泛滥和增加 Rust 代码的安全性。
|
||||
无需担心错过存在非空值的假设让我们对代码更加有信心,为了拥有一个可能为空的值,必须显式的将其放入对应类型的`Option<T>`中。接着,当使用这个值时,必须明确的处理值为空的情况。任何地方一个值不是`Option<T>`类型的话,**可以**安全的假设它的值不为空。这是 Rust 的一个有意为之的设计选择,来限制空值的泛滥和增加 Rust 代码的安全性。
|
||||
|
||||
那么当有一个`Option<T>`的值时,如何从`Some`成员中取出`T`的值来使用它呢?`Option<T>`枚举拥有大量用于各种情况的方法:你可以查看[相关代码][docs]<!-- ignore -->。熟悉`Option<T>`的方法将对你的 Rust 之旅提供巨大的帮助。
|
||||
|
||||
[docs]: ../std/option/enum.Option.html
|
||||
[docs]: https://doc.rust-lang.org/std/option/enum.Option.html
|
||||
|
||||
总的来说,为了使用`Option<T>`值,需要编写处理每个成员的代码。我们想要一些代码只当拥有`Some(T)`值时运行,这些代码允许使用其中的`T`。也希望一些代码当在`None`值时运行,这些代码并没有一个可用的`T`值。`match`表达式就是这么一个处理枚举的控制流结构:它会根据枚举的成员运行不同的代码,这些代码可以使用匹配到的值中的数据。
|
@ -1,17 +1,15 @@
|
||||
## `match`控制流运算符
|
||||
|
||||
> [ch06-02-match.md](https://github.com/rust-lang/book/blob/master/src/ch06-02-match.md)
|
||||
> [ch06-02-match.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-02-match.md)
|
||||
> <br>
|
||||
> commit 396e2db4f7de2e5e7869b1f8bc905c45c631ad7d
|
||||
> commit 64090418c23d615facfe49a8d548ad9baea6b097
|
||||
|
||||
Rust 有一个叫做`match`的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会讲到所有不同种类的模式以及他们的作用。`match`的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
|
||||
Rust 有一个叫做`match`的极为强大的控制流运算符,它允许我们将一个值与一系列的模式相比较并根据匹配的模式执行代码。模式可由字面值、变量、通配符和许多其他内容构成;第十八章会涉及到所有不同种类的模式以及他们的作用。`match`的力量来源于模式的表现力以及编译器检查,它确保了所有可能的情况都得到处理。
|
||||
|
||||
把`match`表达式想象成某种硬币筛选机器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会检查`match`的每一个模式,并且在遇到第一个“符合”的模式时,值会进入相关联的代码块并在执行中被使用。
|
||||
把`match`表达式想象成某种硬币分类器:硬币滑入有着不同大小孔洞的轨道,每一个硬币都会掉入符合它大小的孔洞。同样地,值也会检查`match`的每一个模式,并且在遇到第一个“符合”的模式时,值会进入相关联的代码块并在执行中被使用。
|
||||
|
||||
因为刚刚提到了硬币,让我们用他们来作为一个使用`match`的例子!我们可以编写一个函数来获取一个未知的(美国)硬币,并以一种类似验钞机的方式,确定它是何种硬币并返回它的美分值,如列表 6-3 中所示:
|
||||
|
||||
<figure>
|
||||
|
||||
```rust
|
||||
enum Coin {
|
||||
Penny,
|
||||
@ -30,21 +28,18 @@ fn value_in_cents(coin: Coin) -> i32 {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 6-3: An enum and a `match` expression that has the variants of the enum
|
||||
as its patterns.
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 6-3: An enum and a `match` expression that has
|
||||
the variants of the enum as its patterns.</span>
|
||||
|
||||
拆开`value_in_cents`函数中的`match`来看。首先,我们列出`match`关键字后跟一个表达式,在这个例子中是`coin`的值。这看起来非常像`if`使用的表达式,不过这里有一个非常大的区别:对于`if`,表达式必须返回一个布尔值。而这里它可以是任何类型的。例子中的`coin`的类型是列表 6-3 中定义的`Coin`枚举。
|
||||
|
||||
接下来是`match`的分支。一个分支有两个部分:一个模式和一些代码。第一个分支的模式是值`Coin::Penny`而之后的`=>`运算符将模式和将要运行的代码分开。这里的代码就仅仅是值`1`。每一个分支之间使用逗号分隔。
|
||||
|
||||
当`match`表达式执行时,它将结果值按顺序与每一个分支的模式相比较,如果模式匹配了这个值,这个模式相关联的代码将被执行。如果模式并不匹配这个值,将继续执行下一个分支,非常像一个硬币分类器。可以拥有任意多的分支:列表 6-3 中的`match`有四个分支。
|
||||
|
||||
每个分支相关联的代码是一个表达式,而表达式的结果值将作为整个`match`表达式的返回值。
|
||||
|
||||
如果分支代码较短的话可以不适用大括号,正如列表 6-3 中的每个分支都只是返回一个值。如果想要在分支中运行多行代码,可以使用大括号。例如,如下代码在每次使用`Coin::Penny`调用时都会打印出“Lucky penny!”,同时仍然返回代码块最后的值,`1`:
|
||||
如果分支代码较短的话通常不使用大括号,正如列表 6-3 中的每个分支都只是返回一个值。如果想要在分支中运行多行代码,可以使用大括号。例如,如下代码在每次使用`Coin::Penny`调用时都会打印出“Lucky penny!”,同时仍然返回代码块最后的值,`1`:
|
||||
|
||||
```rust
|
||||
# enum Coin {
|
||||
@ -71,9 +66,7 @@ fn value_in_cents(coin: Coin) -> i32 {
|
||||
|
||||
匹配分支的另一个有用的功能是可以绑定匹配的模式的部分值。这也就是如何从枚举成员中提取值。
|
||||
|
||||
作为一个例子,让我们修改枚举的一个成员来存放数据。1999 年到 2008 年间,美帝在 25 美分的硬币的一侧为 50 个州每一个都印刷了不同的设计。其他的硬币都没有这种区分州的设计,所以只有这些 25 美分硬币有特殊的价值。可以将这些信息加入我们的`enum`,通过改变`Quarter`成员来包含一个`State`值,列表 6-4 中完成了这些修改:
|
||||
|
||||
<figure>
|
||||
作为一个例子,让我们修改枚举的一个成员来存放数据。1999 年到 2008 年间,美帝在 25 美分的硬币的一侧为 50 个州的每一个都印刷了不同的设计。其他的硬币都没有这种区分州的设计,所以只有这些 25 美分硬币有特殊的价值。可以将这些信息加入我们的`enum`,通过改变`Quarter`成员来包含一个`State`值,列表 6-4 中完成了这些修改:
|
||||
|
||||
```rust
|
||||
#[derive(Debug)] // So we can inspect the state in a minute
|
||||
@ -91,17 +84,12 @@ enum Coin {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
<span class="caption">Listing 6-4: A `Coin` enum where the `Quarter` variant
|
||||
also holds a `UsState` value</span>
|
||||
|
||||
Listing 6-4: A `Coin` enum where the `Quarter` variant also holds a `UsState`
|
||||
value
|
||||
想象一下我们的一个朋友尝试收集所有 50 个州的 25 美分硬币。在根据硬币类型分类零钱的同时,也可以报告出每个 25 美分硬币所对应的州名称,这样如果我们的朋友没有的话,他可以把它加入收藏。
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
想象一下我们的一个朋友尝试收集所有 50 个州的 25 美分硬币。在根据硬币类型分类零钱的同时,也可以报告出每个 25 美分硬币所对应的州名称,这样如何我们的朋友没有的话,他可以把它加入收藏。
|
||||
|
||||
在这些代码的匹配表达式中,我们在匹配`Coin::Quarter`成员的分支的模式中增加了一个叫做`state`的变量。当匹配到`Coin::Quarter`时,变量`state`将会绑定 25 美分硬币所对应州的值。接着在代码那个分支中使用`state`,如下:
|
||||
在这些代码的匹配表达式中,我们在匹配`Coin::Quarter`成员的分支的模式中增加了一个叫做`state`的变量。当匹配到`Coin::Quarter`时,变量`state`将会绑定 25 美分硬币所对应州的值。接着在那个分支的代码中使用`state`,如下:
|
||||
|
||||
```rust
|
||||
# #[derive(Debug)]
|
||||
@ -136,12 +124,10 @@ fn value_in_cents(coin: Coin) -> i32 {
|
||||
|
||||
在之前的部分在使用`Option<T>`时我们想要从`Some`中取出其内部的`T`值;也可以像处理`Coin`枚举那样使用`match`处理`Option<T>`!与其直接比较硬币,我们将比较`Option<T>`的成员,不过`match`表达式的工作方式保持不变。
|
||||
|
||||
比如想要编写一个函数,它获取一个`Option<i32>`并且如果其中有一个值,将其加一。如果其中没有值,函数应该返回`None`值并不尝试执行任何操作。
|
||||
比如我们想要编写一个函数,它获取一个`Option<i32>`并且如果其中有一个值,将其加一。如果其中没有值,函数应该返回`None`值并不尝试执行任何操作。
|
||||
|
||||
编写这个函数非常简单,得益于`match`,它将看起来像列表 6-5 中这样:
|
||||
|
||||
<figure>
|
||||
|
||||
```rust
|
||||
fn plus_one(x: Option<i32>) -> Option<i32> {
|
||||
match x {
|
||||
@ -155,12 +141,8 @@ let six = plus_one(five);
|
||||
let none = plus_one(None);
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 6-5: A function that uses a `match` expression on an `Option<i32>`
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 6-5: A function that uses a `match` expression on
|
||||
an `Option<i32>`</span>
|
||||
|
||||
#### 匹配`Some(T)`
|
||||
|
||||
@ -189,7 +171,7 @@ None => None,
|
||||
|
||||
匹配上了!这里没有值来加一,所以程序结束并返回`=>`右侧的值`None`,因为第一个分支就匹配到了,其他的分支将不再比较。
|
||||
|
||||
将`match`与枚举相结合在很多场景中都是有用的。你会在 Rust 代码中看到很多这样的模式:`match`一个枚举,绑定其中的值到一个变量,接着根据其值执行代码。这在一开有点复杂,不过一旦习惯了,你将希望所有语言都拥有它!这一直是用户的最爱。
|
||||
将`match`与枚举相结合在很多场景中都是有用的。你会在 Rust 代码中看到很多这样的模式:`match`一个枚举,绑定其中的值到一个变量,接着根据其值执行代码。这在一开有点复杂,不过一旦习惯了,你会希望所有语言都拥有它!这一直都是用户的最爱。
|
||||
|
||||
### 匹配是穷尽的
|
||||
|
||||
|
@ -1,13 +1,11 @@
|
||||
## `if let`简单控制流
|
||||
|
||||
> [ch06-03-if-let.md](https://github.com/rust-lang/book/blob/master/src/ch06-03-if-let.md)
|
||||
> [ch06-03-if-let.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch06-03-if-let.md)
|
||||
> <br>
|
||||
> commit 396e2db4f7de2e5e7869b1f8bc905c45c631ad7d
|
||||
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
|
||||
|
||||
`if let`语法让我们以一种不那么冗长的方式结合`if`和`let`,来处理匹配一个模式的值而忽略其他的值。考虑列表 6-6 中的程序,它匹配一个`Option<u8>`值并只希望当值是三时执行代码:
|
||||
|
||||
<figure>
|
||||
|
||||
```rust
|
||||
let some_u8_value = Some(0u8);
|
||||
match some_u8_value {
|
||||
@ -16,13 +14,8 @@ match some_u8_value {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 6-6: A `match` that only cares about executing code when the value is
|
||||
`Some(3)`
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 6-6: A `match` that only cares about executing
|
||||
code when the value is `Some(3)`</span>
|
||||
|
||||
我们想要对`Some(3)`匹配进行操作不过不想处理任何其他`Some<u8>`值或`None`值。为了满足`match`表达式(穷尽性)的要求,必须在处理完这唯一的成员后加上`_ => ()`,这样也要增加很多样板代码。
|
||||
|
||||
@ -96,4 +89,4 @@ if let Coin::Quarter(state) = coin {
|
||||
|
||||
你的 Rust 程序现在能够使用结构体和枚举在自己的作用域内表现其内容了。在你的 API 中使用自定义类型保证了类型安全:编译器会确保你的函数只会得到它期望的类型的值。
|
||||
|
||||
为了向你的用户提供一个组织良好的 API,它使用直观且只向用户暴露他们确实需要的部分,那么让我们转向 Rust 的模块系统吧。
|
||||
为了向你的用户提供一个组织良好的 API,它使用起来很直观并且只向用户暴露他们确实需要的部分,那么现在就让我们转向 Rust 的模块系统吧。
|
@ -1,8 +1,8 @@
|
||||
# 模块
|
||||
|
||||
> [ch07-00-modules.md](https://github.com/rust-lang/book/blob/master/src/ch07-00-modules.md)
|
||||
> [ch07-00-modules.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-00-modules.md)
|
||||
> <br>
|
||||
> commit e2a129961ae346f726f8b342455ec2255cdfed68
|
||||
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
|
||||
|
||||
在你刚开始编写 Rust 程序时,代码可能仅仅位于`main`函数里。随着代码数量的增长,最终你会将功能移动到其他函数中,为了复用也为了更好的组织。通过将代码分隔成更小的块,每一个块代码自身就更易于理解。不过当你发现自己有太多的函数了该怎么办呢?Rust 有一个模块系统来处理编写可复用代码同时保持代码组织度的问题。
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
## `mod`和文件系统
|
||||
|
||||
> [ch07-01-mod-and-the-filesystem.md](https://github.com/rust-lang/book/blob/master/src/ch07-01-mod-and-the-filesystem.md)
|
||||
> [ch07-01-mod-and-the-filesystem.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-01-mod-and-the-filesystem.md)
|
||||
> <br>
|
||||
> commit e2a129961ae346f726f8b342455ec2255cdfed68
|
||||
> commit 6fc32eabcd09f7a130094767abadb691dfcdddf7
|
||||
|
||||
我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过我们不再创建一个二进制 crate,而是创建一个库 crate:一个其他人可以作为依赖导入的项目。第二章我们见过的`rand`就是这样的 crate。
|
||||
我们将通过使用 Cargo 创建一个新项目来开始我们的模块之旅,不过不再创建一个二进制 crate,而是创建一个库 crate:一个其他人可以作为依赖导入的项目。第二章我们见过的`rand`就是这样的 crate。
|
||||
|
||||
我们将创建一个提供一些通用网络功能的项目的骨架结构;我们将专注于模块和函数的组织,而不担心函数体中的具体代码。这个项目叫做`communicator`。Cargo 默认会创建一个库 crate 除非指定其他项目类型,所以如果不像一直以来那样加入`--bin`参数则项目将会是一个库:
|
||||
|
||||
@ -34,7 +34,7 @@ Cargo 创建了一个空的测试来帮助我们开始库项目,不像使用`-
|
||||
|
||||
### 模块定义
|
||||
|
||||
对于`communicator`网络库,首先我们要定义一个叫做`network`的模块,它包含一个叫做`connect`的函数定义。Rust 中所有模块的定义以关键字`mod`开始。在 *src/lib.rs* 文件的开头在测试代码的上面增加这些代码:
|
||||
对于`communicator`网络库,首先要定义一个叫做`network`的模块,它包含一个叫做`connect`的函数定义。Rust 中所有模块的定义以关键字`mod`开始。在 *src/lib.rs* 文件的开头在测试代码的上面增加这些代码:
|
||||
|
||||
<span class="filename">Filename: src/lib.rs</span>
|
||||
|
||||
@ -49,8 +49,6 @@ mod network {
|
||||
|
||||
也可以在 *src/lib.rs* 文件中同时存在多个模块。例如,再拥有一个`client`模块,它也有一个叫做`connect`的函数,如列表 7-1 中所示那样增加这个模块:
|
||||
|
||||
|
||||
<figure>
|
||||
<span class="filename">Filename: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
@ -65,19 +63,13 @@ mod client {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
<span class="caption">Listing 7-1: The `network` module and the `client` module
|
||||
defined side-by-side in *src/lib.rs*</span>
|
||||
|
||||
Listing 7-1: The `network` module and the `client` module defined side-by-side
|
||||
in *src/lib.rs*
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
现在我们有了`network::connect`函数和`client::connect`函数。他们可能有着完全不同的功能,同时他们也不会彼此冲突因为他们位于不同的模块。
|
||||
现在我们有了`network::connect`函数和`client::connect`函数。他们可能有着完全不同的功能,同时他们也不会彼此冲突,因为他们位于不同的模块。
|
||||
|
||||
虽然在这个例子中,我们构建了一个库,但是 *src/lib.rs* 并没有什么特殊意义。也可以在 *src/main.rs* 中使用子模块。事实上,也可以将模块放入其他模块中。这有助于随着模块的增长,将相关的功能组织在一起并又保持各自独立。如何选择组织代码依赖于如何考虑代码不同部分之间的关系。例如,对于库的用户来说,`client`模块和它的函数`connect`可能放在`network`命名空间里显得更有道理,如列表 7-2 所示:
|
||||
|
||||
<figure>
|
||||
<span class="filename">Filename: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
@ -92,12 +84,8 @@ mod network {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 7-2: Moving the `client` module inside of the `network` module
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 7-2: Moving the `client` module inside of the
|
||||
`network` module</span>
|
||||
|
||||
在 *src/lib.rs* 文件中,将现有的`mod network`和`mod client`的定义替换为`client`模块作为`network`的一个内部模块。现在我们有了`network::connect`和`network::client::connect`函数:又一次,这两个`connect`函数也不相冲突,因为他们在不同的命名空间中。
|
||||
|
||||
@ -123,7 +111,6 @@ communicator
|
||||
|
||||
位于层级结构中的模块,非常类似计算机领域的另一个我们非常熟悉的结构:文件系统!我们可以利用 Rust 的模块系统连同多个文件一起分解 Rust 项目,这样就不是所有的内容都落到 *src/lib.rs* 中了。作为例子,我们将从列表 7-3 中的代码开始:
|
||||
|
||||
<figure>
|
||||
<span class="filename">Filename: src/lib.rs</span>
|
||||
|
||||
```rust
|
||||
@ -143,13 +130,8 @@ mod network {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 7-3: Three modules, `client`, `network`, and `network::server`, all
|
||||
defined in *src/lib.rs*
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 7-3: Three modules, `client`, `network`, and
|
||||
`network::server`, all defined in *src/lib.rs*</span>
|
||||
|
||||
这是模块层次结构:
|
||||
|
||||
@ -160,6 +142,26 @@ communicator
|
||||
└── server
|
||||
```
|
||||
|
||||
如果这些模块有很多函数,而这些函数又很长,将难以在文件中寻找我们需要的代码。因为这些函数被嵌套进一个或多个模块中,同时函数中的代码也会开始变长。这就有充分的理由将`client`、`network`和`server`每一个模块从 *src/lib.rs* 抽出并放入他们自己的文件中。
|
||||
|
||||
让我们开始把`client`模块提取到另一个文件中。首先,将 *src/lib.rs* 中的`client`模块代码替换为如下:
|
||||
|
||||
<span class="filename">Filename: src/lib.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
mod client;
|
||||
|
||||
mod network {
|
||||
fn connect() {
|
||||
}
|
||||
|
||||
mod server {
|
||||
fn connect() {
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这里我们仍然**定义**了`client`模块,不过去掉了大括号和`client`模块中的定义并替换为一个分号,这使得 Rust 知道去其他地方寻找模块中定义的代码。
|
||||
|
||||
那么现在需要创建对应模块名的外部文件。在 *src/* 目录创建一个 *client.rs* 文件,接着打开它并输入如下内容,它是上一步`client`模块中被去掉的`connect`函数:
|
||||
@ -171,7 +173,7 @@ fn connect() {
|
||||
}
|
||||
```
|
||||
|
||||
注意这个文件中并不需要一个`mod`声明;因为已经在 *src/lib.rs* 中已经使用`mod`声明了`client`模块。这个文件仅仅提供`client`模块的内容。如果在这里加上一个`mod client`,那么就等于给`client`模块增加了一个叫做`client`的子模块!
|
||||
注意这个文件中并不需要一个`mod`声明;因为已经在 *src/lib.rs* 中已经使用`mod`声明了`client`模块。这个文件仅仅提供`client`模块的**内容**。如果在这里加上一个`mod client`,那么就等于给`client`模块增加了一个叫做`client`的子模块了!
|
||||
|
||||
Rust 默认只知道 *src/lib.rs* 中的内容。如果想要对项目加入更多文件,我们需要在 *src/lib.rs* 中告诉 Rust 去寻找其他文件;这就是为什么`mod client`需要被定义在 *src/lib.rs* 而不是在 *src/client.rs*。
|
||||
|
||||
@ -228,7 +230,7 @@ mod server {
|
||||
|
||||
注意这个模块文件中我们也使用了一个`mod`声明;这是因为我们希望`server`成为`network`的一个子模块。
|
||||
|
||||
现在再次运行`cargo build`。成功!不过我们还需要再提取出另一个模块:`server`。因为这是一个子模块————也就是模块中的模块————目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 *src/network.rs* 的第一个修改是用`mod server;`替换`server`模块的内容:
|
||||
现在再次运行`cargo build`。成功!不过我们还需要再提取出另一个模块:`server`。因为这是一个子模块——也就是模块中的模块——目前的将模块提取到对应名字的文件中的策略就不管用了。如果我们仍这么尝试则会出现错误。对 *src/network.rs* 的第一个修改是用`mod server;`替换`server`模块的内容:
|
||||
|
||||
<span class="filename">Filename: src/network.rs</span>
|
||||
|
||||
@ -250,8 +252,6 @@ fn connect() {
|
||||
|
||||
当尝试运行`cargo build`时,会出现如列表 7-4 中所示的错误:
|
||||
|
||||
<figure>
|
||||
|
||||
```text
|
||||
$ cargo build
|
||||
Compiling communicator v0.1.0 (file:///projects/communicator)
|
||||
@ -273,19 +273,14 @@ note: ... or maybe `use` the module `server` instead of possibly redeclaring it
|
||||
| ^^^^^^
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 7-4: Error when trying to extract the `server` submodule into
|
||||
*src/server.rs*
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 7-4: Error when trying to extract the `server`
|
||||
submodule into *src/server.rs*</span>
|
||||
|
||||
这个错误说明“不能在这个位置新声明一个模块”并指出 *src/network.rs* 中的`mod server;`这一行。看来 *src/network.rs* 与 *src/lib.rs* 在某些方面是不同的;让我们继续阅读以理解这是为什么。
|
||||
|
||||
列表 7-4 中间的记录事实上是非常有帮助的,因为它指出了一些我们还未讲到的操作:
|
||||
|
||||
```text
|
||||
```
|
||||
note: maybe move this module `network` to its own directory via `network/mod.rs`
|
||||
```
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
## 使用`pub`控制可见性
|
||||
|
||||
> [ch07-02-controlling-visibility-with-pub.md](https://github.com/rust-lang/book/blob/master/src/ch07-02-controlling-visibility-with-pub.md)
|
||||
> [ch07-02-controlling-visibility-with-pub.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-02-controlling-visibility-with-pub.md)
|
||||
> <br>
|
||||
> commit e2a129961ae346f726f8b342455ec2255cdfed68
|
||||
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
|
||||
|
||||
我们通过将`network`和`network::server`的代码分别移动到 *src/network/mod.rs* 和 *src/network/server.rs* 文件中解决了列表 7-4 中出现的错误信息。现在,`cargo build`能够构建我们的项目,不过仍然有一些警告信息,表示`client::connect`、`network::connect`和`network::server::connect`函数没有被使用:
|
||||
|
||||
@ -26,7 +26,7 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
|
||||
| ^
|
||||
```
|
||||
|
||||
那么为什么会出现这些错误信息呢?我们构建的是一个库,它的函数的目的是被**用户**使用,而不一定要被项目自身使用,所以不应该担心这些函数是未被使用的。创建他们的意义就在于被另一个项目而不是被自己使用。
|
||||
那么为什么会出现这些错误信息呢?我们构建的是一个库,它的函数的目的是被**用户**使用,而不一定要被项目自身使用,所以不应该担心这些`connect`函数是未使用的。创建他们的意义就在于被另一个项目而不是被自己使用。
|
||||
|
||||
为了理解为什么这个程序出现了这些警告,尝试作为另一个项目来使用这个`connect`库,从外部调用他们。为此,通过创建一个包含这些代码的 *src/main.rs* 文件,在与库 crate 相同的目录创建一个二进制 crate:
|
||||
|
||||
@ -58,7 +58,7 @@ error: module `client` is private
|
||||
|
||||
啊哈!这告诉了我们`client`模块是私有的,这也正是那些警告的症结所在。这也是我们第一次在 Rust 上下文中涉及到**公有**和**私有**的概念。Rust 所有代码的默认状态是私有的:除了自己之外别人不允许使用这些代码。如果不在自己的项目中使用一个私有函数,因为程序自身是唯一允许使用这个函数的代码,Rust 会警告说函数未被使用。
|
||||
|
||||
一旦我们指定一个像`client::connect`的函数为公有,不光二进制 crate 中的函数调用会被允许,函数未被使用的警告也会消失。将其标记为公有让 Rust 知道了我们意在使函数在我们程序的外部被使用。现在这个可能的理论上的外部可用性使 Rust 认为这个函数“已经被使用”。因此。当某项被标记为公有,Rust 不再要求它在程序自身被使用并停止警告某项未被使用。
|
||||
一旦我们指定一个像`client::connect`的函数为公有,不光二进制 crate 中的函数调用是允许的,函数未被使用的警告也会消失。将其标记为公有让 Rust 知道了我们意在使函数在我们程序的外部被使用。现在这个可能的理论上的外部可用性使得 Rust 认为这个函数“已经被使用”。因此。当某项被标记为公有,Rust 不再要求它在程序自身被使用并停止警告某项未被使用。
|
||||
|
||||
### 标记函数为公有
|
||||
|
||||
@ -173,7 +173,6 @@ warning: function is never used: `connect`, #[warn(dead_code)] on by default
|
||||
|
||||
让我们看看更多例子作为练习。创建一个新的库项目并在新项目的 *src/lib.rs* 输入列表 7-5 中的代码:
|
||||
|
||||
<figure>
|
||||
<span class="filename">Filename: src/lib.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
@ -197,13 +196,8 @@ fn try_me() {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 7-5: Examples of private and public functions, some of which are
|
||||
incorrect
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 7-5: Examples of private and public functions,
|
||||
some of which are incorrect</span>
|
||||
|
||||
在尝试编译这些代码之前,猜测一下`try_me`函数的哪一行会出错。接着编译项目来看看是否猜对了,然后继续阅读后面关于错误的讨论!
|
||||
|
||||
|
@ -1,12 +1,11 @@
|
||||
## 导入命名
|
||||
|
||||
> [ch07-03-importing-names-with-use.md](https://github.com/rust-lang/book/blob/master/src/ch07-03-importing-names-with-use.md)
|
||||
> [ch07-03-importing-names-with-use.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch07-03-importing-names-with-use.md)
|
||||
> <br>
|
||||
> commit e2a129961ae346f726f8b342455ec2255cdfed68
|
||||
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
|
||||
|
||||
我们已经讲到了如何使用模块名称作为调用的一部分,来调用模块中的函数,如列表 7-6 中所示的`nested_modules`函数调用。
|
||||
|
||||
<figure>
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
@ -23,13 +22,8 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 7-6: Calling a function by fully specifying its enclosing module’s
|
||||
namespaces
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 7-6: Calling a function by fully specifying its
|
||||
enclosing module’s namespaces</span>
|
||||
|
||||
如你所见,指定函数的完全限定名称可能会非常冗长。所幸 Rust 有一个关键字使得这些调用显得更简洁。
|
||||
|
||||
@ -138,7 +132,7 @@ mod tests {
|
||||
}
|
||||
```
|
||||
|
||||
第十二章会更详细的解释测试,不过其部分内容现在应该可以理解了:有一个叫做`tests`的模块紧邻其他模块,同时包含一个叫做`it_works`的函数。即便存在一些特殊注解,`tests`也不过是另外一个模块!所以我们的模块层次结构看起来像这样:
|
||||
第十一章会更详细的解释测试,不过其部分内容现在应该可以理解了:有一个叫做`tests`的模块紧邻其他模块,同时包含一个叫做`it_works`的函数。即便存在一些特殊注解,`tests`也不过是另外一个模块!所以我们的模块层次结构看起来像这样:
|
||||
|
||||
```
|
||||
communicator
|
||||
|
@ -1,8 +1,8 @@
|
||||
# 通用集合类型
|
||||
|
||||
> [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/master/src/ch08-00-common-collections.md)
|
||||
> [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-00-common-collections.md)
|
||||
> <br>
|
||||
> commit 0d229cc5a3da341196e15a6761735b2952281569
|
||||
> commit e6d6caab41471f7115a621029bd428a812c5260e
|
||||
|
||||
Rust 标准库中包含一系列被称为**集合**(*collections*)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就可知并且可以随着程序的运行增长或缩小。每种集合都有着不同能力和代价,而为所处的场景选择合适的集合则是你将要始终发展的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合:
|
||||
|
||||
@ -12,6 +12,6 @@ Rust 标准库中包含一系列被称为**集合**(*collections*)的非常
|
||||
|
||||
对于标准库提供的其他类型的集合,请查看[文档][collections]。
|
||||
|
||||
[collections]: ../std/collections
|
||||
[collections]: https://doc.rust-lang.org/std/collections
|
||||
|
||||
我们将讨论如何创建和更新 vector、字符串和哈希 map,以及他们何以如此特殊。
|
||||
我们将讨论如何创建和更新 vector、字符串和哈希 map,以及他们有什么不同。
|
@ -1,10 +1,10 @@
|
||||
## vector
|
||||
|
||||
> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/master/src/ch08-01-vectors.md)
|
||||
> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-01-vectors.md)
|
||||
> <br>
|
||||
> commit 0d229cc5a3da341196e15a6761735b2952281569
|
||||
> commit 6c24544ba718bce0755bdaf03423af86280051d5
|
||||
|
||||
我们要讲到的第一个类型是`Vec<T>`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个值,它在内存中彼此相邻的排列所有的值。vector 只能储存相同类型的值。他们在拥有一系列的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
|
||||
我们要讲到的第一个类型是`Vec<T>`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个值,它在内存中彼此相邻的排列所有的值。vector 只能储存相同类型的值。他们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
|
||||
|
||||
### 新建 vector
|
||||
|
||||
@ -133,7 +133,10 @@ let row = vec![
|
||||
];
|
||||
```
|
||||
|
||||
Rust 在编译时就必须准确的知道 vector 中类型的原因是它需要知道储存每个元素到底需要多少内存。第二个优点是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加`match`意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。
|
||||
<span class="caption">Listing 8-1: Defining an enum to be able to hold
|
||||
different types of data in a vector</span>
|
||||
|
||||
Rust 在编译时就必须准确的知道 vector 中类型的原因是它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加`match`意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。
|
||||
|
||||
如果在编写程序时不能确切无遗的知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,第十七章会讲到它。
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
## 字符串
|
||||
|
||||
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/src/ch08-02-strings.md)
|
||||
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-02-strings.md)
|
||||
> <br>
|
||||
> commit 65f52921e21ad2e1c79d620fcfd01bde3ee30571
|
||||
> commit d362dadae60a7cc3212b107b9e9562769b0f20e3
|
||||
|
||||
第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解一下它。字符串是新晋 Rustacean 们通常会被困住的领域。这是由于三方面内容的结合:Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些结合起来对于来自其他语言背景的程序员就可能显得很困难了。
|
||||
|
||||
@ -67,7 +67,7 @@ let hello = "Hola";
|
||||
|
||||
`String`的大小可以增长其内容也可以改变,就像可以放入更多数据来改变`Vec`的内容一样。另外,`String`实现了`+`运算符作为级联运算符以便于使用。
|
||||
|
||||
#### 附加字符串
|
||||
#### 使用 push 附加字符串
|
||||
|
||||
可以通过`push_str`方法来附加字符串 slice,从而使`String`变长:
|
||||
|
||||
@ -111,7 +111,7 @@ fn add(self, s: &str) -> String {
|
||||
|
||||
这并不是标准库中实际的签名;那个`add`使用泛型定义。这里的签名使用具体类型代替了泛型,这也正是当使用`String`值调用这个方法会发生的。第十章会讨论泛型。这个签名提供了理解`+`运算那奇怪的部分的线索。
|
||||
|
||||
首先,`s2`使用了`&`,意味着我们使用第二个字符串的**引用**与第一个字符串相加。这是因为`add`函数的`s`参数:只能将`&str`和`String`相加,不能将两个`String`值相加。回忆之前第四章我们讲到`&String`是如何被强转为`&str`的:写成`&s2`的话`String`将会被强转成一个合适的类型`&str`。又因为方法没有获取参数的所有权,所以`s2`在这个操作后仍然有效。
|
||||
首先,`s2`使用了`&`,意味着我们使用第二个字符串的**引用**与第一个字符串相加。这是因为`add`函数的`s`参数:只能将`&str`和`String`相加,不能将两个`String`值相加。不过等一下——正如`add`的第二个参数所指定的,`&s2`的类型是`&String`而不是`&str`。那么为什么代码还能编译呢?之所以能够在`add`调用中使用`&s2`是因为`&String`可以被**强转**(*coerced*)成 `&str`——当`add`函数被调用时,Rust 使用了一个被成为**解引用强制多态**(*deref coercion*)的技术,你可以将其理解为它把`&s2`变成了`&s2[..]`以供`add`函数使用。第十五章会更深入的讨论解引用强制多态。因为`add`没有获取参数的所有权,所以`s2`在这个操作后仍然是有效的`String`。
|
||||
|
||||
其次,可以发现签名中`add`获取了`self`的所有权,因为`self`**没有**使用`&`。这意味着上面例子中的`s1`的所有权将被移动到`add`调用中,之后就不再有效。所以虽然`let s3 = s1 + &s2;`看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取`s1`的所有权,附加上从`s2`中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝不过实际上并没有:这个实现比拷贝要更高效。
|
||||
|
||||
@ -157,7 +157,7 @@ satisfied [--explain E0277]
|
||||
note: the type `std::string::String` cannot be indexed by `_`
|
||||
```
|
||||
|
||||
错误和提示说明了全部问题:Rust 的字符串不支持索引。那么接下来的问题是,为什么不支持呢?为了回答这个问题,我们必须先聊一聊 Rust 如何在内存中储存字符串。
|
||||
错误和提示说明了全部问题:Rust 的字符串不支持索引。那么接下来的问题是,为什么不支持呢?为了回答这个问题,我们必须先聊一聊 Rust 是如何在内存中储存字符串的。
|
||||
|
||||
#### 内部表示
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
## 哈希 map
|
||||
|
||||
> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/master/src/ch08-03-hash-maps.md)
|
||||
> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch08-03-hash-maps.md)
|
||||
> <br>
|
||||
> commit 0d229cc5a3da341196e15a6761735b2952281569
|
||||
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
|
||||
|
||||
最后要介绍的常用集合类型是**哈希 map**(*hash map*)。`HashMap<K, V>`类型储存了一个键类型`K`对应一个值类型`V`的映射。它通过一个**哈希函数**(*hashing function*)来实现映射,它决定了如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表或者关联数组,仅举几例。
|
||||
|
||||
@ -176,4 +176,4 @@ vector、字符串和哈希 map 会在你的程序需要储存、访问和修改
|
||||
|
||||
标准库 API 文档中描述的这些类型的方法将有助于你进行这些练习!
|
||||
|
||||
我们已经开始解除可能会有失败操作的复杂程序了,这也意味着接下来是一个了解错误处理的绝佳时机!
|
||||
我们已经开始接触可能会有失败操作的复杂程序了,这也意味着接下来是一个了解错误处理的绝佳时机!
|
@ -1,10 +1,10 @@
|
||||
# 错误处理
|
||||
|
||||
> [ch09-00-error-handling.md](https://github.com/rust-lang/book/blob/master/src/ch09-00-error-handling.md)
|
||||
> [ch09-00-error-handling.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-00-error-handling.md)
|
||||
> <br>
|
||||
> commit fc825966fabaa408067eb2df3aa45e4fa6644fb6
|
||||
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
|
||||
|
||||
Rust 对可靠性的执着也扩展到了错误处理。错误对于软件来说是不可避免的,所以 Rust 有很多功能来处理当现错误的情况。在很多情况下,Rust 要求你承认出错的可能性可能性并在编译代码之前就采取行动。通过确保不会只有在将代码部署到生产环境之后才会发现错误来使得程序更可靠。
|
||||
Rust 对可靠性的执着也扩展到了错误处理。错误对于软件来说是不可避免的,所以 Rust 有很多功能来处理当现错误的情况。在很多情况下,Rust 要求你承认出错的可能性并在编译代码之前就采取行动。通过确保不会只有在将代码部署到生产环境之后才会发现错误来使得程序更可靠。
|
||||
|
||||
Rust 将错误组合成两个主要类别:**可恢复错误**(*recoverable*)和**不可恢复错误**(*unrecoverable*)。可恢复错误通常代表向用户报告错误和重试操作是合理的情况,比如未找到文件。不可恢复错误通常是 bug 的同义词,比如尝试访问超过数组结尾的位置。
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
## `panic!`与不可恢复的错误
|
||||
|
||||
> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-01-unrecoverable-errors-with-panic.md)
|
||||
> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-01-unrecoverable-errors-with-panic.md)
|
||||
> <br>
|
||||
> commit 380e6ee57c251f5ffa8df4c58b3949405448d914
|
||||
> commit e26bb338ab14b98a850c3464e821d54940a45672
|
||||
|
||||
突然有一天,糟糕的事情发生了,而你对此束手无策。对于这种情况,Rust 有`panic!宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,并接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。
|
||||
突然有一天,糟糕的事情发生了,而你对此束手无策。对于这种情况,Rust 有`panic!`宏。当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug 而且程序员并不清楚该如何处理它。
|
||||
|
||||
> ### Panic 中的栈展开与终止
|
||||
>
|
||||
@ -41,7 +41,7 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
|
||||
|
||||
在这个例子中,被指明的那一行是我们代码的一部分,而且查看这一行的话就会发现`panic!`宏的调用。换句话说,`panic!`可能会出现在我们的代码调用的代码中。错误信息报告的文件名和行号可能指向别人代码中的`panic!`宏调用,而不是我们代码中最终导致`panic!`的那一行。可以使用`panic!`被调用的函数的 backtrace 来寻找(我们代码中出问题的地方)。
|
||||
|
||||
### 使用`panic!`backtrace
|
||||
### 使用`panic!`的 backtrace
|
||||
|
||||
让我们来看看另一个因为我们代码中的 bug 引起的别的库中`panic!`的例子,而不是直接的宏调用:
|
||||
|
||||
@ -76,43 +76,48 @@ error: Process didn't exit successfully: `target/debug/panic` (exit code: 101)
|
||||
|
||||
接下来的几行提醒我们可以设置`RUST_BACKTRACE`环境变量来得到一个 backtrace 来调查究竟是什么导致了错误。让我们来试试看。列表 9-1 显示了其输出:
|
||||
|
||||
<figure>
|
||||
|
||||
```
|
||||
$ RUST_BACKTRACE=1 cargo run
|
||||
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
|
||||
Running `target/debug/panic`
|
||||
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is
|
||||
100', /stable-dist-rustc/build/src/libcollections/vec.rs:1395
|
||||
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', /stable-dist-rustc/build/src/libcollections/vec.rs:1392
|
||||
stack backtrace:
|
||||
1: 0x10922522c -
|
||||
std::sys::imp::backtrace::tracing::imp::write::h1204ab053b688140
|
||||
2: 0x10922649e -
|
||||
std::panicking::default_hook::{{closure}}::h1204ab053b688140
|
||||
3: 0x109226140 - std::panicking::default_hook::h1204ab053b688140
|
||||
4: 0x109226897 -
|
||||
std::panicking::rust_panic_with_hook::h1204ab053b688140
|
||||
5: 0x1092266f4 - std::panicking::begin_panic::h1204ab053b688140
|
||||
6: 0x109226662 - std::panicking::begin_panic_fmt::h1204ab053b688140
|
||||
7: 0x1092265c7 - rust_begin_unwind
|
||||
8: 0x1092486f0 - core::panicking::panic_fmt::h1204ab053b688140
|
||||
9: 0x109248668 -
|
||||
core::panicking::panic_bounds_check::h1204ab053b688140
|
||||
10: 0x1092205b5 - <collections::vec::Vec<T> as
|
||||
core::ops::Index<usize>>::index::h1204ab053b688140
|
||||
11: 0x10922066a - panic::main::h1204ab053b688140
|
||||
12: 0x1092282ba - __rust_maybe_catch_panic
|
||||
13: 0x109226b16 - std::rt::lang_start::h1204ab053b688140
|
||||
14: 0x1092206e9 - main
|
||||
1: 0x560ed90ec04c - std::sys::imp::backtrace::tracing::imp::write::hf33ae72d0baa11ed
|
||||
at /stable-dist-rustc/build/src/libstd/sys/unix/backtrace/tracing/gcc_s.rs:42
|
||||
2: 0x560ed90ee03e - std::panicking::default_hook::{{closure}}::h59672b733cc6a455
|
||||
at /stable-dist-rustc/build/src/libstd/panicking.rs:351
|
||||
3: 0x560ed90edc44 - std::panicking::default_hook::h1670459d2f3f8843
|
||||
at /stable-dist-rustc/build/src/libstd/panicking.rs:367
|
||||
4: 0x560ed90ee41b - std::panicking::rust_panic_with_hook::hcf0ddb069e7abcd7
|
||||
at /stable-dist-rustc/build/src/libstd/panicking.rs:555
|
||||
5: 0x560ed90ee2b4 - std::panicking::begin_panic::hd6eb68e27bdf6140
|
||||
at /stable-dist-rustc/build/src/libstd/panicking.rs:517
|
||||
6: 0x560ed90ee1d9 - std::panicking::begin_panic_fmt::abcd5965948b877f8
|
||||
at /stable-dist-rustc/build/src/libstd/panicking.rs:501
|
||||
7: 0x560ed90ee167 - rust_begin_unwind
|
||||
at /stable-dist-rustc/build/src/libstd/panicking.rs:477
|
||||
8: 0x560ed911401d - core::panicking::panic_fmt::hc0f6d7b2c300cdd9
|
||||
at /stable-dist-rustc/build/src/libcore/panicking.rs:69
|
||||
9: 0x560ed9113fc8 - core::panicking::panic_bounds_check::h02a4af86d01b3e96
|
||||
at /stable-dist-rustc/build/src/libcore/panicking.rs:56
|
||||
10: 0x560ed90e71c5 - <collections::vec::Vec<T> as core::ops::Index<usize>>::index::h98abcd4e2a74c41
|
||||
at /stable-dist-rustc/build/src/libcollections/vec.rs:1392
|
||||
11: 0x560ed90e727a - panic::main::h5d6b77c20526bc35
|
||||
at /home/you/projects/panic/src/main.rs:4
|
||||
12: 0x560ed90f5d6a - __rust_maybe_catch_panic
|
||||
at /stable-dist-rustc/build/src/libpanic_unwind/lib.rs:98
|
||||
13: 0x560ed90ee926 - std::rt::lang_start::hd7c880a37a646e81
|
||||
at /stable-dist-rustc/build/src/libstd/panicking.rs:436
|
||||
at /stable-dist-rustc/build/src/libstd/panic.rs:361
|
||||
at /stable-dist-rustc/build/src/libstd/rt.rs:57
|
||||
14: 0x560ed90e7302 - main
|
||||
15: 0x7f0d53f16400 - __libc_start_main
|
||||
16: 0x560ed90e6659 - _start
|
||||
17: 0x0 - <unknown>
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 9-1: The backtrace generated by a call to `panic!` displayed when the
|
||||
environment variable `RUST_BACKTRACE` is set
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 9-1: The backtrace generated by a call to
|
||||
`panic!` displayed when the environment variable `RUST_BACKTRACE` is set</span>
|
||||
|
||||
这里有大量的输出!backtrace 第 11 行指向了我们程序中引起错误的行:*src/main.rs* 的第四行。backtrace 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
## `Result`与可恢复的错误
|
||||
|
||||
> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/src/ch09-02-recoverable-errors-with-result.md)
|
||||
> [ch09-01-unrecoverable-errors-with-panic.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-02-recoverable-errors-with-result.md)
|
||||
> <br>
|
||||
> commit 0c1d55ef48e5f6cf6a3b221f5b6dd4c922130bb1
|
||||
> commit e6d6caab41471f7115a621029bd428a812c5260e
|
||||
|
||||
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并回应的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而操作就失败,这是我们可能想要创建这个文件而不是终止进程。
|
||||
大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反映的原因失败。例如,如果尝试打开一个文件不过由于文件并不存在而操作就失败,这是我们可能想要创建这个文件而不是终止进程。
|
||||
|
||||
回忆一下第二章“使用`Result`类型来处理潜在的错误”部分中的那个`Result`枚举,它定义有如下连个成员,`Ok`和`Err`:
|
||||
回忆一下第二章“使用`Result`类型来处理潜在的错误”部分中的那个`Result`枚举,它定义有如下两个成员,`Ok`和`Err`:
|
||||
|
||||
[handle_failure]: ch02-00-guessing-game-tutorial.html#handling-potential-failure-with-the-result-type
|
||||
|
||||
```rust
|
||||
enum Result<T, E> {
|
||||
@ -19,7 +21,6 @@ enum Result<T, E> {
|
||||
|
||||
让我们调用一个返回`Result`的函数,因为它可能会失败:如列表 9-2 所示打开一个文件:
|
||||
|
||||
<figure>
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust
|
||||
@ -30,12 +31,7 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 9-2: Opening a file
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 9-2: Opening a file</span>
|
||||
|
||||
如何知道`File::open`返回一个`Result`呢?我们可以查看标准库 API 文档,或者可以直接问编译器!如果给`f`某个我们知道**不是**函数返回值类型的类型注解,接着尝试编译代码,编译器会告诉我们类型不匹配。然后错误信息会告诉我们`f`的类型**应该**是什么,为此我们将`let f`语句改为:
|
||||
|
||||
@ -65,7 +61,6 @@ error[E0308]: mismatched types
|
||||
|
||||
我们需要在列表 9-2 的代码中增加根据`File::open`返回值进行不同处理的逻辑。列表 9-3 展示了一个处理`Result`的基本工具:第六章学习过的`match`表达式。
|
||||
|
||||
<figure>
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust,should_panic
|
||||
@ -83,13 +78,8 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 9-3: Using a `match` expression to handle the `Result` variants we
|
||||
might have
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 9-3: Using a `match` expression to handle the
|
||||
`Result` variants we might have</span>
|
||||
|
||||
注意与`Option`枚举一样,`Result`枚举和其成员也被导入到了 prelude 中,所以就不需要在`match`分支中的`Ok`和`Err`之前指定`Result::`。
|
||||
|
||||
@ -106,7 +96,6 @@ Os { code: 2, message: "No such file or directory" } }', src/main.rs:8
|
||||
|
||||
列表 9-3 中的代码不管`File::open`是因为什么原因失败都会`panic!`。我们真正希望的是对不同的错误原因采取不同的行为:如果`File::open`因为文件不存在而失败,我们希望创建这个文件并返回新文件的句柄。如果`File::open`因为任何其他原因失败,例如没有打开文件的权限,我们仍然希望像列表 9-3 那样`panic!`。让我们看看列表 9-4,其中`match`增加了另一个分支:
|
||||
|
||||
<figure>
|
||||
<span class="filename">Filename: src/main.rs</span>
|
||||
|
||||
```rust,ignore
|
||||
@ -139,16 +128,12 @@ fn main() {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 9-4: Handling different kinds of errors in different ways
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 9-4: Handling different kinds of errors in
|
||||
different ways</span>
|
||||
|
||||
`File::open`返回的`Err`成员中的值类型`io::Error`,它是一个标准库中提供的结构体。这个结构体有一个返回`io::ErrorKind`值的`kind`方法可供调用。`io::ErrorKind`是一个标准库提供的枚举,它的成员对应`io`操作可能导致的不同错误类型。我们感兴趣的成员是`ErrorKind::NotFound`,它代表尝试打开的文件并不存在。
|
||||
|
||||
`if error.kind() == ErrorKind::NotFound`条件被称作 *match guard*:它是一个进一步完善`match`分支模式的额外的条件。这个条件必须为真才能使分支的代码被执行;否则,模式匹配会继续并考虑`match`中的下一个分支。模式中的`ref`是必须的,这样`error`就不会被移动到 guard 条件中而只是仅仅引用它。第十八章会详细解释为什么在模式中使用`ref`而不是`&`来获取一个引用。简而言之,在模式的上下文中,`&`匹配一个引用并返回它的值,而`ref`匹配一个值并返回一个引用。
|
||||
条件`if error.kind() == ErrorKind::NotFound`被称作 *match guard*:它是一个进一步完善`match`分支模式的额外的条件。这个条件必须为真才能使分支的代码被执行;否则,模式匹配会继续并考虑`match`中的下一个分支。模式中的`ref`是必须的,这样`error`就不会被移动到 guard 条件中而只是仅仅引用它。第十八章会详细解释为什么在模式中使用`ref`而不是`&`来获取一个引用。简而言之,在模式的上下文中,`&`匹配一个引用并返回它的值,而`ref`匹配一个值并返回一个引用。
|
||||
|
||||
在 match guard 中我们想要检查的条件是`error.kind()`是否是`ErrorKind`枚举的`NotFound`成员。如果是,尝试用`File::create`创建文件。然而`File::create`也可能会失败,我们还需要增加一个内部`match`语句。当文件不能被打开,会打印出一个不同的错误信息。外部`match`的最后一个分支保持不变这样对任何除了文件不存在的错误会使程序 panic。
|
||||
|
||||
@ -196,8 +181,6 @@ thread 'main' panicked at 'Failed to open hello.txt: Error { repr: Os { code:
|
||||
|
||||
例如,列表 9-5 展示了一个从文件中读取用户名的函数。如果文件不存在或不能读取,这个函数会将这些错误返回给调用它的代码:
|
||||
|
||||
<figure>
|
||||
|
||||
```rust
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
@ -220,12 +203,8 @@ fn read_username_from_file() -> Result<String, io::Error> {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 9-5: A function that returns errors to the calling code using `match`
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 9-5: A function that returns errors to the
|
||||
calling code using `match`</span>
|
||||
|
||||
首先让我们看看函数的返回值:`Result<String, io::Error>`。这意味着函数返回一个`Result<T, E>`类型的值,其中泛型参数`T`的具体类型是`String`,而`E`的具体类型是`io::Error`。如果这个函数没有出任何错误成功返回,函数的调用者会收到一个包含`String`的`Ok`值————函数从文件中读取到的用户名。如果函数遇到任何错误,函数的调用者会收到一个`Err`值,它储存了一个包含更多这个问题相关信息的`io::Error`实例。我们选择`io::Error`作为函数的返回值是因为它正好是函数体中那两个可能会失败的操作的错误返回值:`File::open`函数和`read_to_string`方法。
|
||||
|
||||
@ -239,12 +218,11 @@ Listing 9-5: A function that returns errors to the calling code using `match`
|
||||
|
||||
### 传播错误的捷径:`?`
|
||||
|
||||
列表 9-6 展示了一个`read_username_from_file`的实现,它实现了与列表 9-5 中的代码相同的功能,不过这个实现是使用了问号运算符:
|
||||
|
||||
<figure>
|
||||
列表 9-6 展示了一个`read_username_from_file`的实现,它实现了与列表 9-5 中的代码相同的功能,不过这个实现是使用了问号运算符的:
|
||||
|
||||
```rust
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use std::fs::File;
|
||||
|
||||
fn read_username_from_file() -> Result<String, io::Error> {
|
||||
@ -255,12 +233,8 @@ fn read_username_from_file() -> Result<String, io::Error> {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 9-6: A function that returns errors to the calling code using `?`
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 9-6: A function that returns errors to the
|
||||
calling code using `?`</span>
|
||||
|
||||
`Result`值之后的`?`被定义为与列表 9-5 中定义的处理`Result`值的`match`表达式有着完全相同的工作方式。如果`Result`的值是`Ok`,这个表达式将会返回`Ok`中的值而程序将继续执行。如果值是`Err`,`Err`中的值将作为整个函数的返回值,就好像使用了`return`关键字一样,这样错误值就被传播给了调用者。
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
## `panic!`还是不`panic!`
|
||||
|
||||
> [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)
|
||||
> [ch09-03-to-panic-or-not-to-panic.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch09-03-to-panic-or-not-to-panic.md)
|
||||
> <br>
|
||||
> commit 0c1d55ef48e5f6cf6a3b221f5b6dd4c922130bb1
|
||||
> commit 3f2a1bd8dbb19cc48b210fc4fb35c305c8d81b56
|
||||
|
||||
那么,该如何决定何时应该`panic!`以及何时应该返回`Result`呢?如果代码 panic,就没有恢复的可能。你可以选择对任何错误场景都调用`panic!`,不管是否有可能恢复,不过这样就你代替调用者决定了这是不可恢复的。选择返回`Result`值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为`Err`是不可恢复的,所以他们也可能会调用`panic!`并将可恢复的错误变成了不可恢复的错误。因此返回`Result`是定义可能会失败的函数的一个好的默认选择。
|
||||
那么,该如何决定何时应该`panic!`以及何时应该返回`Result`呢?如果代码 panic,就没有恢复的可能。你可以选择对任何错误场景都调用`panic!`,不管是否有可能恢复,不过这样就是你代替调用者决定了这是不可恢复的。选择返回`Result`值的话,就将选择权交给了调用者,而不是代替他们做出决定。调用者可能会选择以符合他们场景的方式尝试恢复,或者也可能干脆就认为`Err`是不可恢复的,所以他们也可能会调用`panic!`并将可恢复的错误变成了不可恢复的错误。因此返回`Result`是定义可能会失败的函数的一个好的默认选择。
|
||||
|
||||
有一些情况 panic 比返回`Result`更为合适,不过他们并不常见。让我们讨论一下为何在示例、代码原型和测试中,以及那些人们认为不会失败而编译器不这么看的情况下, panic 是合适的,最后会总结一些在库代码中如何决定是否要 panic 的通用指导原则。
|
||||
|
||||
@ -30,7 +30,7 @@ let home = "127.0.0.1".parse::<IpAddr>().unwrap();
|
||||
|
||||
### 错误处理指导原则
|
||||
|
||||
在当有可能会导致有害状态的情况下建议使用`panic!`————在这里,有害状态是指当一些假设、保证、协议或不可变形被打破的状态,例如无效的值、自相矛盾的值或者被传递了不存在的值————外加如下几种情况:
|
||||
在当有可能会导致有害状态的情况下建议使用`panic!`——在这里,有害状态是指当一些假设、保证、协议或不可变性被打破的状态,例如无效的值、自相矛盾的值或者被传递了不存在的值——外加如下几种情况:
|
||||
|
||||
* 有害状态并不包含**预期**会偶尔发生的错误
|
||||
* 之后的代码的运行依赖于不再处于这种有害状态
|
||||
@ -75,8 +75,6 @@ loop {
|
||||
|
||||
相反我们可以创建一个新类型来将验证放入创建其实例的函数中,而不是到处重复这些检查。这样就可以安全的在函数签名中使用新类型并相信他们接收到的值。列表 9-8 中展示了一个定义`Guess`类型的方法,只有在`new`函数接收到 1 到 100 之间的值时才会创建`Guess`的实例:
|
||||
|
||||
<figure>
|
||||
|
||||
```rust
|
||||
struct Guess {
|
||||
value: u32,
|
||||
@ -99,13 +97,8 @@ impl Guess {
|
||||
}
|
||||
```
|
||||
|
||||
<figcaption>
|
||||
|
||||
Listing 9-8: A `Guess` type that will only continue with values between 1 and
|
||||
100
|
||||
|
||||
</figcaption>
|
||||
</figure>
|
||||
<span class="caption">Listing 9-8: A `Guess` type that will only continue with
|
||||
values between 1 and 100</span>
|
||||
|
||||
首先,我们定义了一个包含`u32`类型字段`value`的结构体`Guess`。这里是储存猜测值的地方。
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user