check ch14-02

This commit is contained in:
KaiserY 2017-09-06 14:37:03 +08:00
parent a08cee5374
commit 13882b1c09
7 changed files with 82 additions and 99 deletions

View File

@ -115,7 +115,7 @@ impl Summarizable for WeatherForecast {
另外这段代码假设 `Summarizable` 是一个公有 trait这是因为列表 10-12 中 `trait` 之前使用了 `pub` 关键字。
trait 实现的一个需要注意的限制是:只能在 trait 或对应类型位于我们 crate 本地的时候为其实现 trait。换句话说不允许对外部类型实现外部 trait。例如不能在 `Vec` 上实现 `Display` trait因为 `Display``Vec` 都定义于标准库中。允许在像 `Tweet` 这样作为我们 `aggregator`crate 部分功能的自定义类型上实现标准库中的 trait `Display`。也允许在 `aggregator`crate 中为 `Vec` 实现 `Summarizable`,因为 `Summarizable` 定义与此。这个限制是我们称为 *orphan rule* 的一部分,如果你感兴趣的可以在类型理论中找到它。简单来说,它被称为 orphan rule 是因为其父类型不存在。没有这条规则的话,两个 crate 可以分别对相同类型是实现相同的 trait因而这两个实现会相互冲突Rust 将无从得知应该使用哪一个。因为 Rust 强制执行 orphan rule其他人编写的代码不会破坏你代码反之亦是如此。
trait 实现的一个需要注意的限制是:只能在 trait 或对应类型位于我们 crate 本地的时候为其实现 trait。换句话说不允许对外部类型实现外部 trait。例如不能在 `Vec` 上实现 `Display` trait因为 `Display``Vec` 都定义于标准库中。允许在像 `Tweet` 这样作为我们 `aggregator`crate 部分功能的自定义类型上实现标准库中的 trait `Display`。也允许在 `aggregator`crate 中为 `Vec` 实现 `Summarizable`,因为 `Summarizable` 定义与此。这个限制是我们称为 **孤儿规则***orphan rule*的一部分,如果你感兴趣的可以在类型理论中找到它。简单来说,它被称为 orphan rule 是因为其父类型不存在。没有这条规则的话,两个 crate 可以分别对相同类型是实现相同的 trait因而这两个实现会相互冲突Rust 将无从得知应该使用哪一个。因为 Rust 强制执行 orphan rule其他人编写的代码不会破坏你代码反之亦是如此。
### 默认实现

View File

@ -153,58 +153,22 @@ pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
<span class="caption">列表 13-28第十二章结尾 `search` 函数的定义</span>
可以通过使用迭代器适配器方法来编写更短的代码。这也避免了一个可变的中间 `results` vector 的使用。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去掉可变状态
可以通过使用迭代器适配器方法来编写更短的代码。这也避免了一个可变的中间 `results` vector 的使用。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去掉可变状态可能会使得将来进行并行搜索的增强变得更容易,因为我们不必管理 `results` vector 的并发访问。列表 13-29 展示了该变化:
<span class="filename">文件名: src/lib.rs</span>
另一部分可以利用迭代器的代码位于列表 12-15 中实现的`search`函数中:
<!-- We hadn't had a listing number for this code sample when we submitted
chapter 12; we'll fix the listing numbers in that chapter after you've
reviewed it. /Carol -->
```rust
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let mut results = Vec::new();
for line in contents.lines() {
if line.contains(query) {
results.push(line);
}
}
results
}
```
我们可以用一种更简短的方式来编写这些代码,并避免使用了一个作为可变中间值的`results` vector像这样使用迭代器适配器方法来实现
```rust
fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
```rust,ignore
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
contents.lines()
.filter(|line| line.contains(query))
.collect()
}
```
这里使用了`filter`适配器来只保留`line.contains(query)`为真的那些行。接着使用`collect`将他们放入另一个 vector 中。这就简单多了!
<span class="caption">列表 13-29`search` 函数实现中使用迭代器适配器</span>
也可以对列表 12-16 中定义的`search_case_insensitive`函数使用如下同样的技术:
回忆 `search` 函数的目的是返回所有 `contents` 中包含 `query` 的行。类似于列表 13-19 中的 `filter` 例子,可以使用 `filter` 适配器只保留 `line.contains(query)` 为真的那些行。接着使用 `collect` 将匹配行收集到另一个 vector 中。这样就容易多了!请随意对 `search_case_insensitive` 函数做出同样的使用迭代器方法的修改。
<!-- Similarly, the code snippet that will be 12-16 didn't have a listing
number when we sent you chapter 12, we will fix it. /Carol -->
接下来的逻辑问题就是在代码中应该选择哪种风格:列表 13-28 中的原始实现,或者是列表 13-29 中使用迭代器的版本。大部分 Rust 程序员倾向于使用迭代器风格。开始这有点难以理解,不过一旦你对不同迭代器的工作方式有了感觉之后,迭代器可能会更容易理解。相比摆弄不同的循环并创建新 vector迭代器代码则更关注循环的目的。这抽象出了那些老生常谈的代码这样就更容易看清代码所特有的概念比如迭代器中每个元素必须面对的过滤条件。
```rust
fn search_case_insensitive<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
let query = query.to_lowercase();
contents.lines()
.filter(|line| {
line.to_lowercase().contains(&query)
}).collect()
}
```
看起来还不坏!那么到底该用哪种风格呢?大部分 Rust 程序员倾向于使用迭代器风格。开始这有点难以理解,不过一旦你对不同迭代器的工作方式有了直觉上的理解之后,他们将更加容易理解。相比使用很多看起来大同小异的循环并创建一个 vector抽象出这些老生常谈的代码将使得我们更容易看清代码所特有的概念比如迭代器中用于过滤每个元素的条件。
不过他们真的完全等同吗?当然更底层的循环会更快一些。让我们聊聊性能吧。
不过这两种实现真的完全等同吗?直觉上的假设是更底层的循环会更快一些。让我们聊聊性能吧。

View File

@ -1,17 +1,19 @@
## 性能
## 性能对比:循环 VS 迭代器
> [ch13-04-performance.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-04-performance.md)
> <br>
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
> commit 40910f557c328858f230123d1234c1cb3029dda3
哪一个版本的`grep`函数会更快一些呢:是直接使用`for`循环的版本还是使用迭代器的版本呢?我们将运行一个性能测试,通过将阿瑟·柯南·道尔的“福尔摩斯探案集”的全部内容加载进`String`并寻找其中的单词 "the"。如下是`for`循环版本和迭代器版本的 grep 函数的性能测试结果:
为了决定使用哪个实现,我们需要知道哪个版本的 `search` 函数更快:直接使用 `for` 循环的版本还是使用迭代器的版本。
```
test bench_grep_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_grep_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
我们运行了一个性能测试,通过将阿瑟·柯南·道尔的“福尔摩斯探案集”的全部内容加载进 `String` 并寻找其中的单词 “the”。如下是 `for` 循环版本和迭代器版本的 `search` 函数的性能测试结果:
```text
test bench_search_for ... bench: 19,620,300 ns/iter (+/- 915,700)
test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
```
结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,我们的目的并不是为了证明他们是完全等同的,而是得出一个怎样比较这两种实现方式的基本思路。对于**真正**的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的**零成本抽象***zero-cost abstractions*之一它意味着抽象并不会强加运行时开销它与本贾尼·斯特劳斯特卢普C++ 的设计和实现者所定义的**零开销***zero-overhead*)如出一辙:
结果迭代器版本还要稍微快一点!这里我们将不会查看性能测试的代码,我们的目的并不是为了证明他们是完全等同的,而是得出一个怎样比较这两种实现方式性能的基本思路。对于一个更全面的性能测试,将会检查不同长度的文本、不同的搜索单词、不同长度的单词和所有其他的可变情况。这里所要表达的是:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能代码。迭代器是 Rust 的 **零成本抽象***zero-cost abstractions*之一它意味着抽象并不会强加运行时开销它与本贾尼·斯特劳斯特卢普C++ 的设计和实现者所定义的 **零开销***zero-overhead*)如出一辙:
> In general, C++ implementations obey the zero-overhead principle: What you dont use, you dont pay for. And further: What you do use, you couldnt hand code any better.
>
@ -21,7 +23,7 @@ test bench_grep_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
>
> - 本贾尼·斯特劳斯特卢普 "Foundations of C++"
作为另一个例子,这里有一些自于音频解码器的代码。这些代码使用迭代器链来对作用域中的三个变量进行了某种数学计算:一个叫`buffer`的数据 slice、一个有12个元素的数组`coefficients`、和一个代表位位数的`qlp_shift`。例子中声明了这些变量但并没有提供任何值;虽然这些代码在其上下文之外没有什么意义,不过仍是一个简的现实中的例子,来展示 Rust 如何将高级概念转换为底层代码:
作为另一个例子,这里有一些自于音频解码器的代码。这些代码使用迭代器链来对作用域中的三个变量进行了某种数学计算:一个叫 `buffer` 的数据 slice、一个有 12 个元素的数组 `coefficients`、和一个代表位位数的 `qlp_shift`。例子中声明了这些变量但并没有提供任何值;虽然这些代码在其上下文之外没有什么意义,不过仍是一个简的现实中的例子,来展示 Rust 如何将高级概念转换为底层代码:
```rust,ignore
let buffer: &mut [i32];
@ -40,12 +42,14 @@ for i in 12..buffer.len() {
为了计算 `prediction` 的值,这些代码遍历了 `coefficients` 中的 12 个值,使用 `zip` 方法将系数与 `buffer` 的前 12 个值组合在一起。接着将每一对值相乘,再将所有结果相加,然后将总和右移 `qlp_shift` 位。
像音频解码器这样的程序通常非常看重计算的性能。这里我们创建了一个迭代器使用了两个适配器接着消费了其值。Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历`coefficients`的值完全用不到循环Rust 知道这里会迭代 12 次,所以它“展开”了循环。所有的系数都被储存在了寄存器中(这意味着访问他们非常快)。也没有数组访问边界检查。这是极端有效率的。
像音频解码器这样的程序通常最看重计算的性能。这里我们创建了一个迭代器使用了两个适配器接着消费了其值。Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历 `coefficients` 的值完全用不到循环Rust 知道这里会迭代 12 次所以它“展开”unroll了循环。展开是一种移除循环的控制代码开销并替换为每个迭代中的重复代码的优化。
所有的系数都被储存在了寄存器中,这意味着访问他们非常快。这里也没有运行时数组访问边界检查。所有这些 Rust 能够提供的优化使得结果代码极为高效。
现在知道这些了,请放心大胆的使用迭代器和闭包吧!他们使得代码看起来更高级,但并不为此引入运行时性能损失。
## 总结
闭包和迭代器是 Rust 受函数式编程语言观念所启发的功能。他们对 Rust 直白的表达高级概念的能力有很大贡献。闭包和迭代器的实现,以及 Rust 的零成本抽象,也使得运行时性能不受影响
闭包和迭代器是 Rust 受函数式编程语言观念所启发的功能。他们对 Rust 以底层的性能来明确的表达高级概念的能力有很大贡献。闭包和迭代器的实现达到了不影响运行时性能的程度。这正是 Rust 竭力提供零成本抽象的目标的一部分
现在我们改进了我们 I/O 项目的(代码)表现力,让我们看一看更多`cargo`的功能,他们是如何将项目准备好分享给世界的
现在我们改进了我们 I/O 项目的(代码)表现力,让我们看一看更多 `cargo` 的功能,他们将帮助我们准备好将项目分享给世界

View File

@ -1,15 +1,15 @@
# 更多关于 Cargo 和 Crates.io
# 进一步认识 Cargo 和 Crates.io
> [ch14-00-more-about-cargo.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-00-more-about-cargo.md)
> <br>
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
> commit db6129a30d7c7baed34dd38dbc56f7ed8a66ae92
目前为止本书已经使用过一些 Cargo 的功能了,不过这是最基本的那些。我们使用 Cargo 构建、运行和测试代码不过它还可以做更多。现在就让我们来了解这些其他功能。Cargo 所能做的比本章所涉及的内容还要多;作为一个完整的参考,请查看文档。
我们将要涉及到:
目前为止我们只使用过 Cargo 构建、运行和测试代码的最基本功能,不过它还可以做到更多。这里我们将了解一些 Cargo 其他更高级的功能,他们将展示如何:
* 使用发布配置来自定义构建
* 将库发布到 crates.io
* 使用工作空间来组织更大的项目
* 从 crates.io 安装二进制文件
* 使用自定义的命令来扩展 Cargo
相比本章能够涉及的工作 Cargo 甚至还可以做到更多,关于其功能的全部解释,请查看[文档](http://doc.rust-lang.org/cargo/)

View File

@ -2,25 +2,35 @@
> [ch14-01-release-profiles.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-01-release-profiles.md)
> <br>
> commit 4f2dc564851dc04b271a2260c834643dfd86c724
> commit db6129a30d7c7baed34dd38dbc56f7ed8a66ae92
Cargo 支持一个叫做**发布配置***release profiles*)的概念。这些配置控制各种代码编译参数而且彼此相互独立。在构建的输出中你已经见过了这个功能的影子:
在 Rust 中 **发布配置***release profiles*)是预定义的、可定制的带有不同选项的配置,他们允许程序员更多的控制代码编译的多种选项。每一个配置都彼此相互独立。
```
Cargo 定义了四种有着良好默认值的可用于各自使用场景的配置。Cargo 根据运行的命令来选择不同的配置。不同命令所对应的配置如表格 14-1 所示:
| 命令 | 配置 |
|-------------------------|-----------|
| `cargo build` | `dev` |
| `cargo build --release` | `release` |
| `cargo test` | `test` |
| `cargo doc` | `doc` |
<span class="caption">表格 14-1运行不同 Cargo 命令所使用的配置</span>
这可能很熟悉,他们出现在构建的输出中,他们展示了构建中所使用的配置:
```text
$ cargo build
Finished debug [unoptimized + debuginfo] target(s) in 0.0 secs
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
$ cargo build --release
Finished release [optimized] target(s) in 0.0 secs
```
这里的 "debug" 和 "release" 提示表明编译器在使用不同的配置。Cargo 支持四种配置:
这里的 “dev” 和 “release” 提示表明编译器在使用不同的配置。
* `dev`:用于`cargo build`
* `release`:用于`cargo build --release`
* `test`:用于`cargo test`
* `doc``cargo doc`
### 定制发布配置
可以通过自定义`Cargo.toml`文件中的`[profile.*]`部分来调整这些配置的编译器参数。例如,这里是`dev`和`release`配置的默认参数
Cargo 对每一个配置都有默认设置,当项目的 *Cargo.toml* 文件的 `[profile.*]` 部分没有指定时使用。通过增加任何希望定制的配置对应的 `[profile.*]` 部分,我们可以选择覆盖任意默认设置的子集。例如,如下是 `dev``release` 配置的 `opt-level` 设置的默认值:
```toml
[profile.dev]
@ -30,16 +40,18 @@ opt-level = 0
opt-level = 3
```
`opt-level`设置控制 Rust 会进行何种程度的优化。这个配置的值从 0 到 3。越高的优化级别需要更多的时间。当开发时经常需要编译,你通常希望能在牺牲一些代码性能的情况下编译得快一些。当准备发布时,一次花费更多时间编译来换取每次都要运行的更快的编译结果将更好一些
`opt-level` 设置控制 Rust 会对代码进行何种程度的优化。这个配置的值从 0 到 3。越高的优化级别需要更多的时间编译,所以如果你在进行开发并经常编译,可能会希望在牺牲一些代码性能的情况下编译得快一些。这就是为什么 `dev``opt-level` 默认为 `0`。当你准备发布时,花费更多时间在编译上则更好。只需要在发布模式编译一次,而编译出来的程序则会运行很多次,所以发布模式用更长的编译时间换取运行更快的代码。这正是为什么 `release` 配置的 `opt-level` 默认为 `3`
可以在`Cargo.toml`中覆盖这些默认设置。例如,如果你想在开发时开启一级优化:
我们可以选择通过在 *Cargo.toml* 增加不同的值来覆盖任何默认设置。比如,如果我们想要在开发配置中使用级别 1 的优化,则可以在 *Cargo.toml* 中增加这两行:
<span class="filename">文件名: Cargo.toml</span>
```toml
[profile.dev]
opt-level = 1
```
将覆盖默认的设置`0`,而现在开发构建将获得更多的优化。虽然不如发布构建,但也多少有一些
会覆盖默认的设置 `0`。现在运行 `cargo build`Cargo 将会使用 `dev` 的默认配置加上定制的 `opt-level`。因为 `opt-level` 设置为 `1`Cargo 会比默认进行更多的优化,但是没有发布构建那么多
对于每个配置的设置和其默认值的完整列表,请查看[Cargo 的文档][cargodoc]。

View File

@ -2,21 +2,22 @@
> [ch14-02-publishing-to-crates-io.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-02-publishing-to-crates-io.md)
> <br>
> commit f2eef19b3a39ee68dd363db2fcba173491ba9dc4
> commit 56352c28cf3fe0402fa5a7cba73890e314d720eb
我们曾经在项目中增加 crates.io 上的 crate 作为依赖。也可以选择将代码分享给其他人。Crates.io 用来分发包的源代码,所以它主要用于分发开源代码。
我们曾经在项目中增加 crates.io 上的包作为依赖不过你也可以通过发布自己的包来向它人分享代码。Crates.io 用来分发包的源代码,所以它主要托管开源代码。
Rust 和 Cargo 有一些帮助人们找到和使用你发布的包的功能。我们将介绍这些功能,接着讲到如何发布一个包。
Rust 和 Cargo 有一些帮助它人更方便找到和使用你发布的包的功能。我们将介绍一些这样的功能,接着讲到如何发布一个包。
### 文档注释
在第三章中,我们见到了以`//`开头的注释Rust 还有第二种注释:**文档注释***documentation comment*)。注释固然对阅读代码的人有帮助,也可以生成 HTML 代码来显式公有 API 的文档注释,这有助于那些对如何**使用** crate 有兴趣而不关心如何**实现**的人。注意只有库 crate 能生成文档,因为二进制 crate 并没有人们需要知道如何使用的公有 API。
### 编写有用的文档注释
文档注释使用`///`而不是`//`并支持 Markdown 注解。他们就位于需要文档的项的之前。如下是一个`add_one`函数的文档注释:
准确的包文档有助于其他用户立即如何以及何时使用他们,所以花一些时间编写文档是值得的。第三章中我们讨论了如何使用 `//` 注释 Rust 代码。Rust 也有特定的用于文档的注释类型,通常被称为 **文档注释***documentation comments*),他们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,他们意在让对库感兴趣的程序员理解如何 **使用** 这个 crate而不是它是如何被 **实现** 的。
<span class="filename">Filename: src/lib.rs</span>
文档注释使用 `///` 而不是 `//` 并支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。列表 14-2 展示了一个 `my_crate` crate 中 `add_one` 函数的文档注释:
````rust
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
/// Adds one to the number given.
///
/// # Examples
@ -24,15 +25,14 @@ Rust 和 Cargo 有一些帮助人们找到和使用你发布的包的功能。
/// ```
/// let five = 5;
///
/// assert_eq!(6, add_one(five));
/// assert_eq!(6, my_crate::add_one(5));
/// ```
pub fn add_one(x: i32) -> i32 {
x + 1
}
````
```
<span class="caption">Listing 14-1: A documentation comment for a
function</span>
<span class="caption">列表 14-2一个函数的文档注释</span>
`cargo doc`运行一个由 Rust 分发的工具,`rustdoc`,来为这些注释生成 HTML 文档。可以运行`cargo doc --open`在本地尝试一下,这会构建当前状态的文档(以及 crate 的依赖)并在浏览器中打开。导航到`add_one`函数将会发现文档注释是如何渲染的。

View File

@ -1,14 +1,17 @@
# 高级特征
我们已经走得很远了! 现在我们已经学了使用Rust时99%的需要学习的内容. 在我们做第20章中的项目之前, 让我们来谈谈你可能会遇到的最后的1%的问题. 你可以随便跳过本章, 当你在实作中遇到这些问题时再回过头来学习也无妨; 我们将学习的在这里列出的特性在某些特定的情况下非常有用. 我们不想舍弃这些特性, 但你用到它们的时候确实不多.
> [ch19-00-advanced-features.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch19-00-advanced-features.md)
> <br>
> commit d06a6a181fd61704cbf7feb55bc61d518c6469f9
本章将覆盖如下内容:
我们已经走得很远了!现在我们已经学习了 99% 的编写 Rust 时需要了解的内容。在第二十章开始的新项目之前,让我们聊聊你可能会遇到的最后 1% 的内容。你可以随意跳过本章并在遇到这些问题时再回过头来;这里将要学习的特征在某些非常特定的情况下非常有用。我们并不想我们不想舍弃这些特性,但你会发现不会经常用到他们。
* 不安全的Rust: 用于当你需要弃用Rust的某些保证时你告诉编译器你将负责维护某些保证
* 高级生命周期: 应对复杂情况的额外的生命周期语法
* 高级Traits: 关联类型, 默认类型参数, 完全合格的句法(fully qualified
syntax), 超级trait(supertraits), 和与trait关联的newtype模式
* 高级类型: 关于newtype模式、类型别名、"never"类型和动态sized类型的高级话题
* 高级函数和闭包: 函数指针和返回闭包
本章将覆盖如下内容:
对所有人而言, 这都是一个介绍Rust迷人特性的宝典! 让我们翻开它吧!
* 不安全 Rust用于当需要舍弃 Rust 的某些保证并告诉编译器你将会负责维持这些保证
* 高级生命周期:用于负责情形的额外的生命周期语法
* 高级 trait与 trait 相关的关联类型默认类型参数完全限定语法fully qualified syntaxtraitsupertraits和 newtype 模式
* 高级类型:关于 newtype 模式的更多内容类型别名“never” 类型和动态大小类型
* 高级函数和闭包:函数指针和返回闭包
对所有人而言,这都是一个介绍 Rust 迷人特性的宝典!让我们翻开它吧!