check to ch14-03

This commit is contained in:
KaiserY 2018-01-23 16:47:47 +08:00
parent ddeacb50a5
commit a53133a0d7
14 changed files with 279 additions and 174 deletions

View File

@ -51,7 +51,7 @@ error: `x` does not live long enough
#### 借用检查器
编译器的这一部分叫做 **借用检查器***borrow checker*),它比较作用域来确保所有的借用都是有效的。示例 10-19 展示了与示例 10-18 相同的例子不过带有变量声明周期的注释:
编译器的这一部分叫做 **借用检查器***borrow checker*),它比较作用域来确保所有的借用都是有效的。示例 10-19 展示了与示例 10-18 相同的例子不过带有变量生命周期的注释:
```rust,ignore
{
@ -76,7 +76,7 @@ line and ends with the first closing curly brace on the 7th line. Do you think
the text art comments work or should we make an SVG diagram that has nicer
looking arrows and labels? /Carol -->
我们将 `r` 的生命周期标记为 `'a` 并将 `x` 的生命周期标记为 `'b`。如你所见,内部的 `'b` 块要比外部的生命周期 `'a` 小得多。在编译时Rust 比较这两个生命周期的大小,并发现 `r` 拥有声明周期 `'a`,不过它引用了一个拥有生命周期 `'b` 的对象。程序被拒绝编译,因为生命周期 `'b` 比生命周期 `'a` 要小:被引用的对象比它的引用者存在的时间更短。
我们将 `r` 的生命周期标记为 `'a` 并将 `x` 的生命周期标记为 `'b`。如你所见,内部的 `'b` 块要比外部的生命周期 `'a` 小得多。在编译时Rust 比较这两个生命周期的大小,并发现 `r` 拥有生命周期 `'a`,不过它引用了一个拥有生命周期 `'b` 的对象。程序被拒绝编译,因为生命周期 `'b` 比生命周期 `'a` 要小:被引用的对象比它的引用者存在的时间更短。
让我们看看示例 10-20 中这个并没有产生悬垂引用且可以正确编译的例子:

View File

@ -1,8 +1,8 @@
## 迭代器
## 使用迭代器处理元素序列
> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-02-iterators.md)
> <br>
> commit 40910f557c328858f230123d1234c1cb3029dda3
> commit ceb31210263d49994bbf09456a35a135da690f24
迭代器模式允许你对一个项的序列进行某些处理。**迭代器***iterator*)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
@ -14,9 +14,11 @@ let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
```
<span class="caption">示例 13-13创建迭代器</span>
<span class="caption">示例 13-13创建一个迭代器</span>
创建迭代器之后,可以选择用多种方式利用它。在示例 3-6 中,我们实际上使用了迭代器和 `for` 循环在每一个项上执行了一些代码,直到现在我们才解释了 `iter` 调用做了什么。示例 13-14 中的例子将迭代器的创建和 `for` 循环中的使用分开。迭代器被储存在 `v1_iter` 变量中,而这时没有进行迭代。一旦 `for` 循环开始使用 `v1_iter`,接着迭代器中的每一个元素被用于循环的一次迭代,这会打印出其每一个值:
创建迭代器之后,可以选择用多种方式利用它。在示例 3-4 中,我们使用迭代器和 `for` 循环在每一个项上执行了一些代码,不过直到现在我们掩盖了 `iter` 调用做了什么。
示例 13-14 中的例子将迭代器的创建和 `for` 循环中的使用分开。迭代器被储存在 `v1_iter` 变量中,而这时没有进行迭代。一旦 `for` 循环开始使用 `v1_iter`,接着迭代器中的每一个元素被用于循环的一次迭代,这会打印出其每一个值:
```rust
let v1 = vec![1, 2, 3];
@ -30,11 +32,13 @@ for val in v1_iter {
<span class="caption">示例 13-14在一个 `for` 循环中使用迭代器</span>
在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始的索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 的元素数量。迭代器为我们处理了所有这些逻辑,这减少了重复代码并潜在的消除了混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector 这样可索引的数据结构.让我们看看迭代器是如何做到这些的。
在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始的索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 的元素数量。
迭代器为我们处理了所有这些逻辑,这减少了重复代码并潜在的消除了混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector 这样可索引的数据结构.让我们看看迭代器是如何做到这些的。
### `Iterator` trait 和 `next` 方法
迭代器都实现了一个叫做 `Iterator` 的定义于标准库的 trait。这个 trait 的定义看起来像:
迭代器都实现了一个叫做 `Iterator` 的定义于标准库的 trait。这个 trait 的定义看起来像这样
```rust
trait Iterator {
@ -46,7 +50,7 @@ trait Iterator {
}
```
这里有一下我们还未讲到的新语法:`type Item` 和 `Self::Item`,他们定义了 trait 的 **关联类型***associated type*)。第十九章会深入讲解关联类型,不过现在只需知道这段代码表明实现 `Iterator` trait 要求同时定义一个 `Item` 类型,这个 `Item` 类型被用作 `next` 方法的返回值类型。换句话说,`Item` 类型将是迭代器返回元素的类型。
注意这里有一下我们还未讲到的新语法:`type Item` 和 `Self::Item`,他们定义了 trait 的 **关联类型***associated type*)。第十九章会深入讲解关联类型,不过现在只需知道这段代码表明实现 `Iterator` trait 要求同时定义一个 `Item` 类型,这个 `Item` 类型被用作 `next` 方法的返回值类型。换句话说,`Item` 类型将是迭代器返回元素的类型。
`next``Iterator` 实现者被要求定义的唯一方法。`next` 一次返回迭代器中的一个项,封装在 `Some` 中,当迭代器结束时,它返回 `None`。如果你希望的话可以直接调用迭代器的 `next` 方法;示例 13-15 有一个测试展示了重复调用由 vector 创建的迭代器的 `next` 方法所得到的值:
@ -68,11 +72,11 @@ fn iterator_demonstration() {
<span class="caption">示例 13-15在迭代器上直接调用 `next` 方法</span>
注意 `v1_iter` 需要是可变的:在迭代器上调用 `next` 方法改变了迭代器中用来记录序列位置的状态。换句话说,代码 **消费**consume或使用了迭代器。每一个 `next` 调用都会从迭代器中吃掉一个项。使用 `for` 循环时无需使 `v1_iter` 可变因为 `for` 循环会获取 `v1_iter` 的所有权并在后台使 `v1_iter` 可变。
注意 `v1_iter` 需要是可变的:在迭代器上调用 `next` 方法改变了迭代器中用来记录序列位置的状态。换句话说,代码 **消费**consume或使用了迭代器。每一个 `next` 调用都会从迭代器中消费一个项。使用 `for` 循环时无需使 `v1_iter` 可变因为 `for` 循环会获取 `v1_iter` 的所有权并在后台使 `v1_iter` 可变。
另外需要注意到从 `next` 调用中得到的值是 vector 的不可变引用。`iter` 方法生成一个不可变引用的迭代器。如果我们需要一个获取 `v1` 所有权并返回拥有所有权的迭代器,则可以调用 `into_iter` 而不是 `iter`。类似的,如果我们希望迭代可变引用,则可以调用 `iter_mut` 而不是 `iter`
### `Iterator` trait 中消费迭代器的方法
### 消费迭代器的方法
`Iterator` trait 有一系列不同的由标准库提供默认实现的方法;你可以在 `Iterator` trait 的标准库 API 文档中找到所有这些方法。一些方法在其定义中调用了 `next` 方法,这也就是为什么在实现 `Iterator` trait 时要求实现 `next` 方法的原因。
@ -97,9 +101,11 @@ fn iterator_sum() {
调用 `sum` 之后不再允许使用 `v1_iter` 因为调用 `sum` 时它会获取迭代器的所有权。
### `Iterator` trait 中产生其他迭代器的方法
### 产生其他迭代器的方法
`Iterator` trait 中定义的另一类方法会产生其他的迭代器。这些方法被称为 **迭代器适配器***iterator adaptors*),他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。示例 13-17 展示了一个调用迭代器适配器方法 `map` 的例子,该 `map` 方法使用闭包来调用每个元素以生成新的迭代器。 这里的闭包创建了一个新的迭代器,其中 `vector` 中的每个元素都被加1。不过这些代码会产生一个警告
`Iterator` trait 中定义了另一类方法,被称为 **迭代器适配器***iterator adaptors*),他们允许我们将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果。
示例 13-17 展示了一个调用迭代器适配器方法 `map` 的例子,该 `map` 方法使用闭包来调用每个元素以生成新的迭代器。 这里的闭包创建了一个新的迭代器,对其中 vector 中的每个元素都被加 1。不过这些代码会产生一个警告
<span class="filename">文件名: src/main.rs</span>
@ -114,9 +120,9 @@ v1.iter().map(|x| x + 1);
得到的警告是:
```text
warning: unused result which must be used: iterator adaptors are lazy and do
nothing unless consumed
--> src/main.rs:4:1
warning: unused `std::iter::Map` which must be used: iterator adaptors are lazy
and do nothing unless consumed
--> src/main.rs:4:5
|
4 | v1.iter().map(|x| x + 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^
@ -124,9 +130,11 @@ nothing unless consumed
= note: #[warn(unused_must_use)] on by default
```
示例 13-17 中的代码实际上并没有做任何事;所指定的闭包从未被调用过。警告提醒了我们为什么:迭代器适配器是惰性的,而这里我们可能意在消费迭代器。
示例 13-17 中的代码实际上并没有做任何事;所指定的闭包从未被调用过。警告提醒了我们为什么:迭代器适配器是惰性的,而这里我们需要消费迭代器。
为了修复这个警告并消费迭代器获取有用的结果,我们将使用第十二章简要讲到的 `collect` 方法。这个方法消费迭代器并将结果收集到一个数据结构中。在示例 13-18 中,我们将遍历由 `map` 调用生成的迭代器的结果收集到一个 vector 中,它将会含有原始 vector 中每个元素加一的结果:
为了修复这个警告并消费迭代器获取有用的结果,我们将使用第十二章简要讲到的 `collect` 方法。这个方法消费迭代器并将结果收集到一个数据结构中。
在示例 13-18 中,我们将遍历由 `map` 调用生成的迭代器的结果收集到一个 vector 中,它将会含有原始 vector 中每个元素加一的结果:
<span class="filename">文件名: src/main.rs</span>
@ -142,20 +150,22 @@ assert_eq!(v2, vec![2, 3, 4]);
因为 `map` 获取一个闭包,可以指定任何希望在遍历的每个元素上执行的操作。这是一个展示如何使用闭包来自定义行为同时又复用 `Iterator` trait 提供的迭代行为的绝佳例子。
### 使用闭包获取环境与迭代器
### 使用闭包获取环境
现在我们介绍了迭代器,让我们展示一个通过使用 `filter` 迭代器适配器和捕获环境的闭包的常规用例。迭代器的 `filter` 方法获取一个使用迭代器的每一个项并返回布尔值的闭包。如果闭包返回 `true`,其值将会包含在 `filter` 提供的新迭代器中。如果闭包返回 `false`,其值不会包含在结果迭代器中。示例 13-19 展示了使用 `filter` 和一个捕获环境中变量 `shoe_size` 的闭包,这样闭包就可以遍历一个 `Shoe` 结构体集合以便只返回指定大小的鞋子:
现在我们介绍了迭代器,让我们展示一个通过使用 `filter` 迭代器适配器和捕获环境的闭包的常规用例。迭代器的 `filter` 方法获取一个使用迭代器的每一个项并返回布尔值的闭包。如果闭包返回 `true`,其值将会包含在 `filter` 提供的新迭代器中。如果闭包返回 `false`,其值不会包含在结果迭代器中。
示例 13-19 展示了使用 `filter` 和一个捕获环境中变量 `shoe_size` 的闭包,这样闭包就可以遍历一个 `Shoe` 结构体集合以便只返回指定大小的鞋子:
<span class="filename">文件名: src/lib.rs</span>
```rust,test_harness
#[derive(PartialEq, Debug)]
struct Shoe {
size: i32,
size: u32,
style: String,
}
fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: i32) -> Vec<Shoe> {
fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
shoes.into_iter()
.filter(|s| s.size == shoe_size)
.collect()
@ -183,7 +193,11 @@ fn filters_by_size() {
<span class="caption">示例 13-19使用 `filter` 方法和一个捕获 `shoe_size` 的闭包</span>
`shoes_in_my_size` 函数获取一个鞋子 vector 的所有权和一个鞋子大小作为参数。它返回一个只包含指定大小鞋子的 vector。在 `shoes_in_my_size` 函数体中调用了 `into_iter` 来创建一个获取 vector 所有权的迭代器。接着调用 `filter` 将这个迭代器适配成只含有闭包返回 `true` 元素的新迭代器。我们指定的闭包从环境中捕获了 `shoe_size` 变量并使用其值与每一只鞋的大小作比较,只保留指定大小的鞋子。最终,调用 `collect` 将迭代器适配器返回的值收集进一个 vector 并返回。
`shoes_in_my_size` 函数获取一个鞋子 vector 的所有权和一个鞋子大小作为参数。它返回一个只包含指定大小鞋子的 vector。
`shoes_in_my_size` 函数体中调用了 `into_iter` 来创建一个获取 vector 所有权的迭代器。接着调用 `filter` 将这个迭代器适配成只含有闭包返回 `true` 元素的新迭代器。
闭包从环境中捕获了 `shoe_size` 变量并使用其值与每一只鞋的大小作比较,只保留指定大小的鞋子。最终,调用 `collect` 将迭代器适配器返回的值收集进一个 vector 并返回。
这个测试展示当调用 `shoes_in_my_size` 时,我们只会得到与指定值相同大小的鞋子。
@ -191,7 +205,7 @@ fn filters_by_size() {
我们已经展示了可以通过在 vector 上调用 `iter`、`into_iter` 或 `iter_mut` 来创建一个迭代器。也可以用标准库中其他的集合类型创建迭代器,比如哈希 map。另外可以实现 `Iterator` trait 来创建任何我们希望的迭代器。正如之前提到的,定义中唯一要求提供的方法就是 `next` 方法。一旦定义了它,就可以使用所有其他由 `Iterator` trait 提供的拥有默认实现的方法来创建自定义迭代器了!
我们将要创建的迭代器只会从 1 数到 5。首先我们会创建一个结构体来存放一些值,接着实现 `Iterator` trait 将这个结构体放入迭代器中并在此实现中使用其值。
作为展示,让我们创建一个只会从 1 数到 5 的迭代器。首先,创建一个结构体来存放一些值,接着实现 `Iterator` trait 将这个结构体放入迭代器中并在此实现中使用其值。
示例 13-20 有一个 `Counter` 结构体定义和一个创建 `Counter` 实例的关联函数 `new`
@ -240,11 +254,13 @@ impl Iterator for Counter {
<span class="caption">示例 13-21`Counter` 结构体上实现 `Iterator` trait</span>
这里将迭代器的关联类型 `Item` 设置为 `u32`,意味着迭代器会返回 `u32` 值集合。再一次,这里仍无需担心关联类型,第十九章会讲到。我们希望迭代器对其内部状态加一,这也就是为何将 `count` 初始化为 0我们希望迭代器首先返回 1。如果 `count` 值小于 6`next` 会返回封装在 `Some` 中的当前值,不过如果 `count` 大于或等于 6迭代器会返回 `None`
这里将迭代器的关联类型 `Item` 设置为 `u32`,意味着迭代器会返回 `u32` 值集合。再一次,这里仍无需担心关联类型,第十九章会讲到。
我们希望迭代器对其内部状态加一,这也就是为何将 `count` 初始化为 0我们希望迭代器首先返回 1。如果 `count` 值小于 6`next` 会返回封装在 `Some` 中的当前值,不过如果 `count` 大于或等于 6迭代器会返回 `None`
#### 使用 `Counter` 迭代器的 `next` 方法
一旦实现了 `Iterator` trait我们就有了一个迭代器示例 13-22 展示了一个测试用来演示现在我们可以使用 `Counter` 结构体的迭代器功能,通过直接调用 `next` 方法,正如示例 13-15 中从 vector 创建的迭代器那样:
一旦实现了 `Iterator` trait我们就有了一个迭代器示例 13-22 展示了一个测试用来演示使用 `Counter` 结构体的迭代器功能,通过直接调用 `next` 方法,正如示例 13-15 中从 vector 创建的迭代器那样:
<span class="filename">文件名: src/lib.rs</span>
@ -332,6 +348,6 @@ fn using_other_iterator_trait_methods() {
<span class="caption">示例 13-23使用自定义的 `Counter` 迭代器的多种方法</span>
注意 `zip` 只产生4对值;理论上第五对值 `(5, None)` 从未被产生,因为 `zip` 在任一输入迭代器返回 `None` 时也返回 `None`
注意 `zip` 只产生对值;理论上第五对值 `(5, None)` 从未被产生,因为 `zip` 在任一输入迭代器返回 `None` 时也返回 `None`
所有这些方法调用都是可能的,因为我们通过指定 `next` 如何工作来实现 `Iterator` trait 而标准库则提供其他调用 `next` 的默认方法实现。

View File

@ -2,13 +2,13 @@
> [ch13-03-improving-our-io-project.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-03-improving-our-io-project.md)
> <br>
> commit 714be7f0d6b2f6110afe8808a7f528f9eae75c61
> commit 2bcb126815a381acc3d46b0d6fc382cb4c98fbc5
我们可以使用迭代器来改进第十二章中 I/O 项目的实现来使得代码更简洁明了。让我们看看迭代器如何能够改进 `Config::new` 函数和 `search` 函数的实现。
有了这些关于迭代器的新知识,我们可以使用迭代器来改进第十二章中 I/O 项目的实现来使得代码更简洁明了。让我们看看迭代器如何能够改进 `Config::new` 函数和 `search` 函数的实现。
### 使用迭代器并去掉 `clone`
在示例 12-6 中,我们增加了一些代码获取一个 `String` slice 并创建一个 `Config` 结构体的实例,他们索引 slice 中的值并克隆这些值以便 `Config` 结构体可以拥有这些值。在示例 13-24 中原原本本的重现了第十二章结尾 `Config::new` 函数的实现:
在示例 12-6 中,我们增加了一些代码获取一个 `String` slice 并创建一个 `Config` 结构体的实例,他们索引 slice 中的值并克隆这些值以便 `Config` 结构体可以拥有这些值。在示例 13-24 中原原本本的重现了第十二章结尾示例 12-23 中 `Config::new` 函数的实现:
<span class="filename">文件名: src/lib.rs</span>
@ -35,13 +35,15 @@ impl Config {
起初这里需要 `clone` 的原因是参数 `args` 中有一个 `String` 元素的 slice`new` 函数并不拥有 `args`。为了能够返回 `Config` 实例的所有权,我们需要克隆 `Config` 中字段 `query``filename` 的值,这样 `Config` 实例就能拥有这些值。
通过迭代器的新知识,我们可以将 `new` 函数改为获取一个有所有权的迭代器作为参数而不是借用 slice。我们将使用迭代器功能之前检查 slice 长度和索引特定位置的代码。这会清理 `Config::new` 的工作因为迭代器会负责访问这些值。
通过迭代器的新知识,我们可以将 `new` 函数改为获取一个有所有权的迭代器作为参数而不是借用 slice。我们将使用迭代器功能之前检查 slice 长度和索引特定位置的代码。这会明确 `Config::new` 的工作因为迭代器会负责访问这些值。
一旦 `Config::new` 获取了迭代器的所有权并不再使用借用的索引操作,就可以将迭代器中的 `String` 值移动到 `Config` 中,而不是调用 `clone` 分配新的空间。
#### 直接使用 `env::args` 返回的迭代器
在 I/O 项目的 *src/main.rs* 中,让我们修改第十二章结尾 `main` 函数中的这些代码:
打开 I/O 项目的 *src/main.rs* 文件,它看起来应该像这样:
<span class="filename">文件名: src/main.rs</span>
```rust,ignore
fn main() {
@ -52,10 +54,12 @@ fn main() {
process::exit(1);
});
// ...snip...
// --snip--
}
```
我们会修改第十二章结尾示例 12-24 中的 `main` 函数的开头为示例 13-25 中的代码。直到同时更新 `Config::new` 这些代码还不能编译:
将他们改为如示例 13-25 所示:
<span class="filename">文件名: src/main.rs</span>
@ -67,7 +71,7 @@ fn main() {
process::exit(1);
});
// ...snip...
// --snip--
}
```
@ -75,21 +79,21 @@ fn main() {
`env::args` 函数返回一个迭代器!不同于将迭代器的值收集到一个 vector 中接着传递一个 slice 给 `Config::new`,现在我们直接将 `env::args` 返回的迭代器的所有权传递给 `Config::new`
接下来需要更新 `Config::new` 的定义。在 I/O 项目的 *src/lib.rs* 中,将 `Config::new` 的签名改为如示例 13-26 所示:
接下来需要更新 `Config::new` 的定义。在 I/O 项目的 *src/lib.rs* 中,将 `Config::new` 的签名改为如示例 13-26 所示。这仍然不能编译因为我们还需更新函数体
<span class="filename">文件名: src/lib.rs</span>
```rust,ignore
impl Config {
pub fn new(args: std::env::Args) -> Result<Config, &'static str> {
// ...snip...
pub fn new(mut args: std::env::Args) -> Result<Config, &'static str> {
// --snip--
```
<span class="caption">示例 13-26更新 `Config::new` 的签名来接受一个迭代器</span>
`env::args` 函数的标准库文档展示了其返回的迭代器类型是 `std::env::Args`。需要更新 `Config::new` 函数的签名中 `args` 参数的类型为 `std::env::Args` 而不是 `&[String]`
`env::args` 函数的标准库文档展示了其返回的迭代器类型是 `std::env::Args`。需要更新 `Config::new` 函数的签名中 `args` 参数的类型为 `std::env::Args` 而不是 `&[String]`因为这里需要获取 `args` 的所有权且通过迭代改变 `args`,我们可以在 `args` 参数前指定 `mut` 关键字使其可变。
#### 使用 `Iterator` trait 方法带起索引
#### 使用 `Iterator` trait 方法代替索引
接下来修复 `Config::new` 的函数体。标准库文档也提到了 `std::env::Args` 实现了 `Iterator` trait所以可以在其上调用 `next` 方法!示例 13-27 更新了示例 12-23 中的代码为使用 `next` 方法:
@ -120,9 +124,7 @@ impl Config {
let case_sensitive = env::var("CASE_INSENSITIVE").is_err();
Ok(Config {
query, filename, case_sensitive
})
Ok(Config { query, filename, case_sensitive })
}
}
```
@ -133,7 +135,7 @@ impl Config {
### 使用迭代器适配器来使代码更简明
I/O 项目中其他可以利用迭代器优势的地方位于 `search` 函数,在示例 13-28 中重现了第十二章结尾的此函数定义:
I/O 项目中其他可以利用迭代器优势的地方位于 `search` 函数,在示例 13-28 中重现了第十二章结尾示例 12-19 中此函数的定义:
<span class="filename">文件名: src/lib.rs</span>

View File

@ -2,7 +2,7 @@
> [ch13-04-performance.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch13-04-performance.md)
> <br>
> commit 40910f557c328858f230123d1234c1cb3029dda3
> commit 2bcb126815a381acc3d46b0d6fc382cb4c98fbc5
为了决定使用哪个实现,我们需要知道哪个版本的 `search` 函数更快:直接使用 `for` 循环的版本还是使用迭代器的版本。
@ -13,7 +13,9 @@ 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.
>
@ -23,7 +25,7 @@ test bench_search_iter ... bench: 19,234,900 ns/iter (+/- 657,200)
>
> - 本贾尼·斯特劳斯特卢普 "Foundations of C++"
作为另一个例子,这里有一些取自于音频解码器的代码。这些代码使用迭代器链来对作用域中的三个变量进行了某种数学计算:一个叫 `buffer` 的数据 slice、一个有 12 个元素的数组 `coefficients`、和一个代表位移位数的 `qlp_shift`。例子中声明了这些变量但并没有提供任何值;虽然这些代码在其上下文之外没有什么意义,不过仍是一个简明的现实中的例子,来展示 Rust 如何将高级概念转换为底层代码:
作为另一个例子,这里有一些取自于音频解码器的代码。解码算法使用线性预测数学运算linear prediction mathematical operation来根据之前样本的线性函数预测将来的值。这些代码使用迭代器链来对作用域中的三个变量进行了某种数学计算:一个叫 `buffer` 的数据 slice、一个有 12 个元素的数组 `coefficients`、和一个代表位移位数的 `qlp_shift`。例子中声明了这些变量但并没有提供任何值;虽然这些代码在其上下文之外没有什么意义,不过仍是一个简明的现实中的例子,来展示 Rust 如何将高级概念转换为底层代码:
```rust,ignore
let buffer: &mut [i32];
@ -42,11 +44,9 @@ for i in 12..buffer.len() {
为了计算 `prediction` 的值,这些代码遍历了 `coefficients` 中的 12 个值,使用 `zip` 方法将系数与 `buffer` 的前 12 个值组合在一起。接着将每一对值相乘,再将所有结果相加,然后将总和右移 `qlp_shift` 位。
像音频解码器这样的程序通常最看重计算的性能。这里我们创建了一个迭代器使用了两个适配器接着消费了其值。Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历 `coefficients` 的值完全用不到循环Rust 知道这里会迭代 12 次所以它“展开”unroll了循环。展开是一种移除循环控制代码开销并替换为每个迭代中的重复代码的优化。
像音频解码器这样的程序通常最看重计算的性能。这里我们创建了一个迭代器使用了两个适配器接着消费了其值。Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历 `coefficients` 的值完全用不到循环Rust 知道这里会迭代 12 次所以它“展开”unroll了循环。展开是一种移除循环控制代码开销并替换为每个迭代中的重复代码的优化。
所有的系数都被储存在了寄存器中,这意味着访问他们非常快。这里也没有运行时数组访问边界检查。所有这些 Rust 能够提供的优化使得结果代码极为高效。
现在知道这些了,请放心大胆的使用迭代器和闭包吧!他们使得代码看起来更高级,但并不为此引入运行时性能损失。
所有的系数都被储存在了寄存器中,这意味着访问他们非常快。这里也没有运行时数组访问边界检查。所有这些 Rust 能够提供的优化使得结果代码极为高效。现在知道这些了,请放心大胆的使用迭代器和闭包吧!他们使得代码看起来更高级,但并不为此引入运行时性能损失。
## 总结

View File

@ -2,14 +2,14 @@
> [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 db6129a30d7c7baed34dd38dbc56f7ed8a66ae92
> commit ff93f82ff63ade5a352d9ccc430945d4ec804cdf
目前为止我们只使用过 Cargo 构建、运行和测试代码的最基本功能,不过它还可以做到更多。这里我们将了解一些 Cargo 其他更高级的功能,他们将展示如何:
目前为止我们只使用过 Cargo 构建、运行和测试代码的最基本功能,不过它还可以做到更多。这里我们将了解一些 Cargo 其他更高级的功能,他们将展示如何:
* 使用发布配置来自定义构建
* 将库发布到 crates.io
* 将库发布到 [crates.io](https://crates.io)<!-- ignore -->
* 使用工作空间来组织更大的项目
* 从 crates.io 安装二进制文件
* 从 [crates.io](https://crates.io)<!-- ignore --> 安装二进制文件
* 使用自定义的命令来扩展 Cargo
相比本章能够涉及的工作 Cargo 甚至还可以做到更多,关于其功能的全部解释,请查看[文档](http://doc.rust-lang.org/cargo/)
相比本章能够涉及的工作 Cargo 甚至还可以做到更多,关于其功能的全部解释,请查看 [文档](http://doc.rust-lang.org/cargo/)

View File

@ -1,23 +1,14 @@
## 发布配置
## 采用发布配置自定义构建
> [ch14-01-release-profiles.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-01-release-profiles.md)
> <br>
> commit db6129a30d7c7baed34dd38dbc56f7ed8a66ae92
> commit ff93f82ff63ade5a352d9ccc430945d4ec804cdf
在 Rust 中 **发布配置***release profiles*)是预定义的、可定制的带有不同选项的配置,他们允许程序员更多的控制代码编译的多种选项。每一个配置都彼此相互独立。
Cargo 定义了四种有着良好默认值的可用于各自使用场景的配置。Cargo 根据运行的命令来选择不同的配置。不同命令所对应的配置如表格 14-1 所示:
Cargo 有两个主要的配置:运行 `cargo build` 时采用的 `dev` 配置和运行 `cargo build --release``release` 配置。`dev` 配置被定义为开发时的好的默认配置,`release` 配置则有着良好的发布构建的默认配置。
| 命令 | 配置 |
|-------------------------|-----------|
| `cargo build` | `dev` |
| `cargo build --release` | `release` |
| `cargo test` | `test` |
| `cargo doc` | `doc` |
<span class="caption">表格 14-1运行不同 Cargo 命令所使用的配置</span>
这可能很熟悉,他们出现在构建的输出中,他们展示了构建中所使用的配置:
我们应该很熟悉这些配置名称因为他们出现在构建的输出中,这会展示构建所使用的配置:
```text
$ cargo build
@ -26,11 +17,11 @@ $ cargo build --release
Finished release [optimized] target(s) in 0.0 secs
```
这里的 “dev” 和 “release” 提示表明编译器在使用不同的配置。
构建输出中的 `dev``release` 表明编译器在使用不同的配置。
### 定制发布配置
Cargo 对每一个配置都有默认设置,当项目的 *Cargo.toml* 文件中没有任何 `[profile.*]` 部分的时候。通过增加任何希望定制的配置对应的 `[profile.*]` 部分,我们可以选择覆盖任意默认设置的子集。例如,如下是 `dev``release` 配置的 `opt-level` 设置的默认值:
Cargo 对每一个配置都有默认设置,当项目的 *Cargo.toml* 文件的 `[profile.*]` 部分没有指定时使用。通过增加任何希望定制的配置对应的 `[profile.*]` 部分,我们可以选择覆盖任意默认设置的子集。例如,如下是 `dev``release` 配置的 `opt-level` 设置的默认值:
<span class="filename">文件名: Cargo.toml</span>
```toml
[profile.dev]
@ -53,6 +44,4 @@ opt-level = 1
这会覆盖默认的设置 `0`。现在运行 `cargo build`Cargo 将会使用 `dev` 的默认配置加上定制的 `opt-level`。因为 `opt-level` 设置为 `1`Cargo 会比默认进行更多的优化,但是没有发布构建那么多。
对于每个配置的设置和其默认值的完整列表,请查看[Cargo 的文档][cargodoc]。
[cargodoc]: http://doc.crates.io/
对于每个配置的设置和其默认值的完整列表,请查看 [Cargo 的文档](https://doc.rust-lang.org/cargo/)。

View File

@ -2,18 +2,18 @@
> [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 56352c28cf3fe0402fa5a7cba73890e314d720eb
> commit ff93f82ff63ade5a352d9ccc430945d4ec804cdf
我们曾经在项目中增加 crates.io 上的包作为依赖不过你也可以通过发布自己的包来向它人分享代码。Crates.io 用来分发包的源代码,所以它主要托管开源代码。
我们曾经在项目中使用 [crates.io](https://crates.io)<!-- ignore --> 上的包作为依赖,不过你也可以通过发布自己的包来向它人分享代码。[crates.io](https://crates.io)<!-- ignore --> 用来分发包的源代码,所以它主要托管开源代码。
Rust 和 Cargo 有一些帮助它人更方便找到和使用你发布的包的功能。我们将介绍一些这样的功能,接着讲到如何发布一个包。
### 编写有用的文档注释
准确的包文档有助于其他用户立即如何以及何时使用他们,所以花一些时间编写文档是值得的。第三章中我们讨论了如何使用 `//` 注释 Rust 代码。Rust 也有特定的用于文档的注释类型,通常被称为 **文档注释***documentation comments*),他们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,他们意在让对库感兴趣的程序员理解如何 **使用** 这个 crate而不是它是如何被 **实现** 的。
准确的包文档有助于其他用户理解如何以及何时使用他们,所以花一些时间编写文档是值得的。第三章中我们讨论了如何使用 `//` 注释 Rust 代码。Rust 也有特定的用于文档的注释类型,通常被称为 **文档注释***documentation comments*),他们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,他们意在让对库感兴趣的程序员理解如何 **使用** 这个 crate而不是它是如何被 **实现** 的。
文档注释使用 `///` 而不是 `//` 并支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。示例 14-2 展示了一个 `my_crate` crate 中 `add_one` 函数的文档注释:
文档注释使用 `///` 而不是 `//` 并支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。示例 14-1 展示了一个 `my_crate` crate 中 `add_one` 函数的文档注释:
<span class="filename">文件名: src/lib.rs</span>
@ -32,29 +32,29 @@ pub fn add_one(x: i32) -> i32 {
}
```
<span class="caption">示例 14-2:一个函数的文档注释</span>
<span class="caption">示例 14-1:一个函数的文档注释</span>
这里,我们提供了一个 `add_one` 函数工作的描述,接着开始了一个标题为 “Examples” 的部分,和展示如何使用 `add_one` 函数的代码。可以运行 `cargo doc` 来生成这个文档注释的 HTML 文档。这个命令运行由 Rust 分发的工具 `rustdoc` 并将生成的 HTML 文档放入 *target/doc* 目录。
为了方便起见,运行 `cargo doc --open` 会构建当前 crate 文档(同时还有所有 ceate 依赖的文档)的 HTML 并在浏览器中打开。导航到 `add_one` 函数将会发现文档注释的文本是如何渲染的,如图 13-3 所示:
为了方便起见,运行 `cargo doc --open` 会构建当前 crate 文档(同时还有所有 crate 依赖的文档)的 HTML 并在浏览器中打开。导航到 `add_one` 函数将会发现文档注释的文本是如何渲染的,如图 14-1 所示:
<img alt="`my_crate` 的 `add_one` 函数所渲染的文档注释 HTML" src="img/trpl14-03.png" class="center" />
<img alt="`my_crate` 的 `add_one` 函数所渲染的文档注释 HTML" src="img/trpl14-01.png" class="center" />
<span class="caption">图 14-3`add_one` 函数的文档注释 HTML</span>
<span class="caption">图 14-1`add_one` 函数的文档注释 HTML</span>
#### 常用(文档注释)部分
示例 14-2 中使用了 `# Examples` Markdown 标题在 HTML 中创建了一个以 “Examples” 为标题的部分。一些其他经常在文档注释中使用的部分有:
示例 14-1 中使用了 `# Examples` Markdown 标题在 HTML 中创建了一个以 “Examples” 为标题的部分。其他一些 crate 作者经常在文档注释中使用的部分有:
- Panics这个函数可能会 `panic!` 的场景。并不希望程序崩溃的函数调用者应该确保他们不会在这些情况下调用此函数。
- Errors如果这个函数返回 `Result`,此部分描述可能会出现何种错误以及什么情况会造成这些错误,这有助于调用者编写代码来采用不同的方式处理不同的错误。
- Safety如果这个函数使用 `unsafe` 代码(这会在第十九章讨论),这一部分应该会涉及到期望函数调用者支持的确保 `unsafe` 块中代码正常工作的不变条件invariants
- **Panics**:这个函数可能会 `panic!` 的场景。并不希望程序崩溃的函数调用者应该确保他们不会在这些情况下调用此函数。
- **Errors**:如果这个函数返回 `Result`,此部分描述可能会出现何种错误以及什么情况会造成这些错误,这有助于调用者编写代码来采用不同的方式处理不同的错误。
- **Safety**:如果这个函数使用 `unsafe` 代码(这会在第十九章讨论),这一部分应该会涉及到期望函数调用者支持的确保 `unsafe` 块中代码正常工作的不变条件invariants
大部分文档注释不需要所有这些部分,不过这是一个提醒你检查调用你代码的人有兴趣了解的内容的列表。
#### 文档注释作为测试
在文档注释中增加示例代码块是一个清楚的表明如何使用库的方法,这么做还有一个额外的好处:`cargo test` 也会像测试那样运行文档中的示例代码!没有什么比有例子的文档更好的了!也没有什么比不能正常工作的例子更糟的了,因为代码在编写文档时已经改变。尝试 `cargo test` 运行像示例 14-2`add_one` 函数的文档;应该在测试结果中看到像这样的部分:
在文档注释中增加示例代码块是一个清楚的表明如何使用库的方法,这么做还有一个额外的好处:`cargo test` 也会像测试那样运行文档中的示例代码!没有什么比有例子的文档更好的了!也没有什么比不能正常工作的例子更糟的了,因为代码在编写文档时已经改变。尝试 `cargo test` 运行像示例 14-1`add_one` 函数的文档;应该在测试结果中看到像这样的部分:
```text
Doc-tests my_crate
@ -62,16 +62,16 @@ pub fn add_one(x: i32) -> i32 {
running 1 test
test src/lib.rs - add_one (line 5) ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
现在尝试改变函数或例子来使例子中的 `assert_eq!` 产生 panic。再次运行 `cargo test`,你将会看到文档测试捕获到了例子与代码不再同步!
#### 注释包含项的结构
还有另一种风格的文档注释,`//!`,这为包含注释的项,而不是注释之后的项增加文档。这通常用于 crate 根文件或模块的根文件为 crate 或模块整体提供文档。
还有另一种风格的文档注释,`//!`,这为包含注释的项,而不是注释之后的项增加文档。这通常用于 crate 根文件(通常是 *src/lib.rs*或模块的根文件为 crate 或模块整体提供文档。
作为一个例子,如果我们希望增加描述包含 `add_one` 函数的 `my_crate` crate 目的的文档,可以在 *src/lib.rs* 开头增加以 `//!` 开头的注释,如示例 14-4 所示:
作为一个例子,如果我们希望增加描述包含 `add_one` 函数的 `my_crate` crate 目的的文档,可以在 *src/lib.rs* 开头增加以 `//!` 开头的注释,如示例 14-2 所示:
<span class="filename">文件名: src/lib.rs</span>
@ -82,22 +82,21 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
//! calculations more convenient.
/// Adds one to the number given.
// ...snip...
// --snip--
```
<span class="caption">示例 14-4`my_crate` crate 整体的文档</span>
<span class="caption">示例 14-2`my_crate` crate 整体的文档</span>
注意 `//!` 的最后一行之后没有任何代码。因为他们以 `//!` 开头而不是 `///`,这是属于包含此注释的项而不是注释之后项的文档。在这个情况中,包含这个注释的项是 *src/lib.rs* 文件,也就是 crate 根文件。这些注释描述了整个 crate。
如果运行 `cargo doc --open`,将会发现这些注释显示在 `my_crate` 文档的首页,位于 crate 中公有项列表之上,如图 14-5 所示:
如果运行 `cargo doc --open`,将会发现这些注释显示在 `my_crate` 文档的首页,位于 crate 中公有项列表之上,如图 14-2 所示:
<img alt="crate 整体注释所渲染的 HTML 文档" src="img/trpl14-05.png" class="center" />
<img alt="crate 整体注释所渲染的 HTML 文档" src="img/trpl14-02.png" class="center" />
<span class="caption">图 14-5:包含 `my_crate` 整体描述的注释所渲染的文档</span>
<span class="caption">图 14-2:包含 `my_crate` 整体描述的注释所渲染的文档</span>
位于项之中的文档注释对于描述 crate 和模块特别有用。使用他们描述其容器整体的目的来帮助 crate 用户理解你的代码组织。
### 使用 `pub use` 导出合适的公有 API
第七章介绍了如何使用 `mod` 关键字来将代码组织进模块中,如何使用 `pub` 关键字将项变为公有,和如何使用 `use` 关键字将项引入作用域。然而对你开发来说很有道理的结果可能对用户来说就不太方便了。你可能希望将结构组织进有多个层次的层级中,不过想要使用被定义在很深层级中的类型的人可能很难发现这些类型是否存在。他们也可能会厌烦 `use my_crate::some_module::another_module::UsefulType;` 而不是 `use my_crate::UsefulType;` 来使用类型。
@ -106,7 +105,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
好消息是,如果结果对于用户来说 **不是** 很方便,你也无需重新安排内部组织:你可以选择使用 `pub use` 重导出re-export项来使公有结构不同于私有结构。重导出获取位于一个位置的公有项并将其公开到另一个位置好像它就定义在这个新位置一样。
例如,假设我们创建了一个模块化了充满艺术化气息的库 `art`。在这个库中是一个包含两个枚举 `PrimaryColor``SecondaryColor` 的模块 `kinds`,以及一个包含函数 `mix` 的模块 `utils`,如示例 14-6 所示:
例如,假设我们创建了一个模块化了充满艺术化气息的库 `art`。在这个库中是一个包含两个枚举 `PrimaryColor``SecondaryColor` 的模块 `kinds`,以及一个包含函数 `mix` 的模块 `utils`,如示例 14-3 所示:
<span class="filename">文件名: src/lib.rs</span>
@ -137,23 +136,22 @@ pub mod utils {
/// Combines two primary colors in equal amounts to create
/// a secondary color.
pub fn mix(c1: PrimaryColor, c2: PrimaryColor) -> SecondaryColor {
// ...snip...
# SecondaryColor::Green
// --snip--
}
}
```
<span class="caption">示例 14-6:一个库 `art` 其组织包含 `kinds``utils` 模块</span>
<span class="caption">示例 14-3:一个库 `art` 其组织包含 `kinds``utils` 模块</span>
`cargo doc` 所生成的 crate 文档首页如图 14-7 所示:
`cargo doc` 所生成的 crate 文档首页如图 14-3 所示:
<img alt="包含 `kinds``utils` 模块的 `art`" src="img/trpl14-07.png" class="center" />
<img alt="包含 `kinds``utils` 模块的 `art`" src="img/trpl14-03.png" class="center" />
<span class="caption">图 14-7:包含 `kinds``utils` 模块的库 `art` 的文档首页</span>
<span class="caption">图 14-3:包含 `kinds``utils` 模块的库 `art` 的文档首页</span>
注意 `PrimaryColor``SecondaryColor` 类型没有在首页中列出,`mix` 函数也是。必须点击 `kinds``utils` 才能看到他们。
另一个依赖这个库的 crate 需要 `use` 语句来导入 `art` 中的项,这包含指定其当前定义的模块结构。示例 14-8 展示了一个使用 `art` crate 中 `PrimaryColor``mix` 项的 crate 的例子:
另一个依赖这个库的 crate 需要 `use` 语句来导入 `art` 中的项,这包含指定其当前定义的模块结构。示例 14-4 展示了一个使用 `art` crate 中 `PrimaryColor``mix` 项的 crate 的例子:
<span class="filename">文件名: src/main.rs</span>
@ -170,11 +168,11 @@ fn main() {
}
```
<span class="caption">示例 14-8:一个通过导出内部结构使用 `art` crate 中项的 crate</span>
<span class="caption">示例 14-4:一个通过导出内部结构使用 `art` crate 中项的 crate</span>
示例 14-8 中使用 `art` crate 代码的作者不得不搞清楚 `PrimaryColor` 位于 `kinds` 模块而 `mix` 位于 `utils` 模块。`art` crate 的模块结构相比使用它的开发者来说对编写它的开发者更有意义。其内部的 `kinds` 模块和 `utils` 模块的组织结构并没有对尝试理解如何使用它的人提供任何有价值的信息。`art` crate 的模块结构因不得不搞清楚所需的内容在何处和必须在 `use` 语句中指定模块名称而显得混乱和不便。
示例 14-4 中使用 `art` crate 代码的作者不得不搞清楚 `PrimaryColor` 位于 `kinds` 模块而 `mix` 位于 `utils` 模块。`art` crate 的模块结构相比使用它的开发者来说对编写它的开发者更有意义。其内部的 `kinds` 模块和 `utils` 模块的组织结构并没有对尝试理解如何使用它的人提供任何有价值的信息。`art` crate 的模块结构因不得不搞清楚所需的内容在何处和必须在 `use` 语句中指定模块名称而显得混乱和不便。
为了从公有 API 中去掉 crate 的内部组织,我们可以采用示例 14-6 中的 `art` crate 并增加 `pub use` 语句来重导出项到顶层结构,如示例 14-9 所示:
为了从公有 API 中去掉 crate 的内部组织,我们可以采用示例 14-3 中的 `art` crate 并增加 `pub use` 语句来重导出项到顶层结构,如示例 14-5 所示:
<span class="filename">文件名: src/lib.rs</span>
@ -188,23 +186,23 @@ pub use kinds::SecondaryColor;
pub use utils::mix;
pub mod kinds {
// ...snip...
// --snip--
}
pub mod utils {
// ...snip...
// --snip--
}
```
<span class="caption">示例 14-9:增加 `pub use` 语句重导出项</span>
<span class="caption">示例 14-5:增加 `pub use` 语句重导出项</span>
现在此 crate 由 `cargo doc` 生成的 API 文档会在首页列出重导出的项以及其链接,如图 14-10 所示,这就使得这些类型易于查找。
现在此 crate 由 `cargo doc` 生成的 API 文档会在首页列出重导出的项以及其链接,如图 14-4 所示,这就使得这些类型易于查找。
<img alt="Rendered documentation for the `art` crate with the re-exports on the front page" src="img/trpl14-10.png" class="center" />
<img alt="Rendered documentation for the `art` crate with the re-exports on the front page" src="img/trpl14-04.png" class="center" />
<span class="caption">图 14-10`art` 文档的首页,这里列出了重导出的项</span>
`art` crate 的用户仍然可以看见和选择使用示例 14-8 中的内部结构,或者可以使用示例 14-9 中更为方便的结构,如示例 14-11 所示:
`art` crate 的用户仍然可以看见和选择使用示例 14-3 中的内部结构,或者可以使用示例 14-4 中更为方便的结构,如示例 14-6 所示:
<span class="filename">文件名: src/main.rs</span>
@ -215,31 +213,33 @@ use art::PrimaryColor;
use art::mix;
fn main() {
// ...snip...
// --snip--
}
```
<span class="caption">示例 14-11一个使用 `art` crate</span>
<span class="caption">示例 14-6一个使用 `art` crate 中重导出项的程序</span>
对于有很多嵌套模块的情况,使用 `pub use` 将类型重导出到顶级结构对于使用 crate 的人来说将会是大为不同的体验。
创建一个有用的公有 API 结构更像是一门艺术而非科学,你可以反复检视他们来找出最适合用户的 API。选择 `pub use` 提供了组织 crate 内部结构和与终端用户体现解耦的灵活性。观察一些你所安装的 crate 的代码来看看其内部结构是否不同于公有 API。
创建一个有用的公有 API 结构更像是一门艺术而非科学,你可以反复检视他们来找出最适合用户的 API。选择 `pub use` 提供了解耦组织 crate 内部结构和与终端用户体现的灵活性。观察一些你所安装的 crate 的代码来看看其内部结构是否不同于公有 API。
### 创建 Crates.io 账号
在你可以发布任何 crate 之前,需要在 crates.io 上注册账号并获取一个 API token。为此访问位于 *https://crates.io* 的官网并使用 GitHub 账号登陆————目前 GitHub 账号是必须的,不过将来该网站可能会支持其他创建账号的方法。一旦登陆之后,查看位于 *https://crates.io/me* 的账户设置页面并获取 API token。接着使用该 API token 运行 `cargo login` 命令,像这样:
在你可以发布任何 crate 之前,需要在 [crates.io](https://crates.io)<!-- ignore --> 上注册账号并获取一个 API token。为此访问位于 [crates.io](https://crates.io)<!-- ignore --> 的首页并使用 GitHub 账号登陆————目前 GitHub 账号是必须的,不过将来该网站可能会支持其他创建账号的方法。一旦登陆之后,查看位于 [https://crates.io/me/](https://crates.io/me/)<!-- ignore --> 的账户设置页面并获取 API token。接着使用该 API token 运行 `cargo login` 命令,像这样:
```text
$ cargo login abcdefghijklmnopqrstuvwxyz012345
```
这个命令会通知 Cargo 你的 API token 并将其储存在本地的 *~/.cargo/config* 文件中。注意这个 token 是一个 **秘密****secret**)且不应该与其他人共享。如果因为任何原因与他人共享了这个信息,应该立即重新生成这个 token。
这个命令会通知 Cargo 你的 API token 并将其储存在本地的 *~/.cargo/credentials* 文件中。注意这个 token 是一个 **秘密****secret**)且不应该与其他人共享。如果因为任何原因与他人共享了这个信息,应该立即到 [crates.io](https://crates.io)<!-- ignore --> 重新生成这个 token。
### 发布新 crate 之前
有了账号之后,比如说你已经有一个希望发布的 crate。在发布之前你需要在 crate 的 *Cargo.toml* 文件的 `[package]` 部分增加一些本 crate 的元信息metadata
首先 crate 需要一个唯一的名称。虽然在本地开发 crate 时,可以使用任何你喜欢的名称。不过 Crates.io 上的 crate 名称遵守先到先得的分配原则。一旦某个 crate 名称被使用,其他人就不能再发布这个名称的 crate 了。请在网站上搜索你希望使用的名称来找出它是否已被使用。如果没有,修改 *Cargo.toml*`[package]` 里的名称为你希望用于发布的名称,像这样:
首先 crate 需要一个唯一的名称。虽然在本地开发 crate 时,可以使用任何你喜欢的名称。不过 [crates.io](https://crates.io)<!-- ignore --> 上的 crate 名称遵守先到先得的分配原则。一旦某个 crate 名称被使用,其他人就不能再发布这个名称的 crate 了。请在网站上搜索你希望使用的名称来找出它是否已被使用。如果没有,修改 *Cargo.toml*`[package]` 里的名称为你希望用于发布的名称,像这样:
<span class="filename">文件名: Cargo.toml</span>
```toml
[package]
@ -253,7 +253,7 @@ $ cargo publish
Updating registry `https://github.com/rust-lang/crates.io-index`
warning: manifest has no description, license, license-file, documentation,
homepage or repository.
...snip...
--snip--
error: api errors: missing or empty metadata fields: description, license.
```
@ -261,6 +261,8 @@ error: api errors: missing or empty metadata fields: description, license.
描述通常是一两句话,因为它会出现在 crate 的搜索结果中和 crate 页面里。对于 `license` 字段,你需要一个 **license 标识符值***license identifier value*。Linux 基金会位于 *http://spdx.org/licenses/* 的 Software Package Data Exchange (SPDX) 列出了可以使用的标识符。例如,为了指定 crate 使用 MIT License增加 `MIT` 标识符:
<span class="filename">文件名: Cargo.toml</span>
```toml
[package]
name = "guessing_game"
@ -269,17 +271,19 @@ license = "MIT"
如果你希望使用不存在于 SPDX 的 license则需要将 license 文本放入一个文件,将该文件包含进项目中,接着使用 `license-file` 来指定文件名而不是使用 `license` 字段。
关于项目所适用的 license 指导超出了本书的范畴。很多 Rust 社区成员选择与 Rust 自身相同的 license这是一个双许可的 `MIT/Apache-2.0`————这展示了也可以通过斜杠来分隔来指定多个 license 标识符。
关于项目所适用的 license 指导超出了本书的范畴。很多 Rust 社区成员选择与 Rust 自身相同的 license这是一个双许可的 `MIT OR Apache-2.0` ———— 这展示了也可以通过 `OR` 来分隔来为项目指定多个 license 标识符。
那么,有了唯一的名称、版本号、由 `cargo new` 新建项目时增加的作者信息、描述和所选择的 license已经准备好发布的项目的 *Cargo.toml* 文件可能看起来像这样:
<span class="filename">文件名: Cargo.toml</span>
```toml
[package]
name = "guessing_game"
version = "0.1.0"
authors = ["Your Name <you@example.com>"]
description = "A fun game where you guess what number the computer has chosen."
license = "MIT/Apache-2.0"
license = "MIT OR Apache-2.0"
[dependencies]
```
@ -288,11 +292,11 @@ license = "MIT/Apache-2.0"
### 发布到 Crates.io
现在我们创建了一个账号,保存了 API token为 crate 选择了一个名字,并指定了所需的元数据,你已经准备好发布了!发布 crate 会上传特定版本的 crate 到 crates.io 以供他人使用。
现在我们创建了一个账号,保存了 API token为 crate 选择了一个名字,并指定了所需的元数据,你已经准备好发布了!发布 crate 会上传特定版本的 crate 到 [crates.io](https://crates.io)<!-- ignore --> 以供他人使用。
发布 crate 时请多加小心,因为发布是 **永久性的***permanent*)。对应版本不可能被覆盖,其代码也不可能被删除。Crates.io 的一个主要目标是作为一个代码的永久文档服务器,这样所有依赖 Crates.io 中 crate 的项目都能一直正常工作。允许删除版本将不可能满足这个目标。然而,可以被发布的版本号却没有限制。
发布 crate 时请多加小心,因为发布是 **永久性的***permanent*)。对应版本不可能被覆盖,其代码也不可能被删除。[crates.io](https://crates.io)<!-- ignore --> 的一个主要目标是作为一个代码的永久文档服务器,这样所有依赖 [crates.io](https://crates.io)<!-- ignore --> 中 crate 的项目都能一直正常工作。允许删除版本将不可能满足这个目标。然而,可以被发布的版本号却没有限制。
让我们再次运行`cargo publish`命令。这次它应该会成功:
再次运行 `cargo publish` 命令。这次它应该会成功:
```text
$ cargo publish
@ -327,7 +331,6 @@ $ cargo yank --vers 1.0.1
也可以撤销撤回操作,并允许项目可以再次开始依赖某个版本,通过在命令上增加 `--undo`
```text
$ cargo yank --vers 1.0.1 --undo
```

View File

@ -2,38 +2,146 @@
> [ch14-03-cargo-workspaces.md](https://github.com/rust-lang/book/blob/master/second-edition/src/ch14-03-cargo-workspaces.md)
> <br>
> commit 6e53771a409794d9933c2a31310d78149b7e0534
> commit a59537604248f2970e0831d5ead9f6fac2cdef84
第十二章中,我们构建一个包含二进制 crate 和库 crate 的包。你可能会发现,随着项目开发的深入,库 crate 持续增大,而你希望将其进一步拆分成多个库 crate。对于这种情况Cargo 提供了一个叫 **工作空间***workspaces*)的功能,它可以帮助我们管理多个相关的并行开发的包。
第十二章中,我们构建一个包含二进制 crate 和库 crate 的包。你可能会发现,随着项目开发的深入,库 crate 持续增大,而你希望将其进一步拆分成多个库 crate。对于这种情况Cargo 提供了一个叫 **工作空间***workspaces*)的功能,它可以帮助我们管理多个相关的协同开发的包。
**工作空间** 是一系列共享同样的 *Cargo.lock* 和输出目录的包。让我们使用工作空间创建一个项目,这里采用常见的代码这样就可以关注工作空间的结构了。这里有一个使用了两个库的二进制项目:一个库会提供 `add_one` 方法而第二个会提供 `add_two` 方法。让我们以为这个二进制项目创建一个新 crate 作为开始:
**工作空间** 是一系列共享同样的 *Cargo.lock* 和输出目录的包。让我们使用工作空间创建一个项目,这里采用常见的代码这样就可以关注工作空间的结构了。有多种组织工作空间的方式;我们将展示一个常用方法。我们的工作空间有一个二进制项目和两个库。二进制项目会提供作为命令行工具的主要功能,它会依赖另两个库。一个库会提供 `add_one` 方法而第二个会提供 `add_two` 方法。这三个 crate 将会是相同工作空间的一部分。让我们以新建工作空间目录开始:
```text
$ mkdir add
$ cd add
```
在 add* 目录中,创建 *Cargo.toml* 文件。这个 *Cargo.toml* 文件配置了整个工作空间。它不会包含 `[package]` 或其他我们在 *Cargo.toml* 中见过的元信息。相反,它以 `[workspace]` 部分作为开始,并通过指定 *adder* 的路径来为工作空间增加成员,如下会加入二进制 crate
<span class="filename">文件名: Cargo.toml</span>
```toml
[workspace]
members = [
"adder",
]
```
接下来,在 *add* 目录运行 `cargo new` 新建 `adder` 二进制 crate
```text
$ cargo new --bin adder
Created binary (application) `adder` project
$ cd adder
```
我们需要修改二进制包的 *Cargo.toml* 并增加一个 `[workspace]` 部分来告诉 Cargo 包 `adder` 是一个工作空间。在文件末尾增加如下内容:
到此为止,可以运行 `cargo build` 来构建工作空间。*add* 目录中的文件应该看起来像这样:
```text
├── Cargo.lock
├── Cargo.toml
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
```
工作空间在顶级目录有一个 *target* 目录;`adder` 并没有自己的 *target* 目录。即使进入 *adder* 目录运行 `cargo build`,构建结果也位于 *add/target* 而不是 *add/adder/target*。工作空间中的 crate 之间相互依赖。如果每个 crate 有其自己的 *target* 目录,为了在自己的 *target* 目录中生成构建结果,工作空间中的每一个 crate 都不得不相互重新编译其他 crate。通过共享一个 *target* 目录,工作空间可以避免其他 crate 多余的重复构建。
### 在工作空间中创建第二个 crate
接下来,让我们在工作空间中指定另一个成员 crate。这个 crate 位于 *add-one* 目录中,所以修改顶级 *Cargo.toml* 为也包含 *add-one* 路径:
<span class="filename">文件名: Cargo.toml</span>
```toml
[workspace]
members = [
"adder",
"add-one",
]
```
类似于很多 Cargo 的功能,工作空间支持配置惯例:只要遵循这些惯例就无需在 *Cargo.toml* 中增加更多的配置来定义工作空间了。
接着新生成一个叫做 `add-one` 的库:
### 指定工作空间的依赖
```text
$ cargo new add-one
Created library `add-one` project
```
工作空间惯例表明任何顶级 crate 依赖的位于任意子目录的 crate 都是工作空间的一部分。任何 crate无论是否在工作空间中可以在 *Cargo.toml* 中使用 `path` 属性来指定它拥有本地目录中的 crate 作为依赖。如果 crate 拥有 `[workspace]` 部分并指定了路径依赖,而这些路径是 crate 的子目录,则这些相关的 crate 被认为是工作空间的一部分。让我们在顶级的 `adder` crate 的 *Cargo.toml* 中为其指定位于 `add-one` 子目录的 `add-one` crate 作为依赖,通过这样修改 *Cargo.toml*
现在 *add* 目录应该有如下目录和文件:
```text
├── Cargo.lock
├── Cargo.toml
├── add-one
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── adder
│ ├── Cargo.toml
│ └── src
│ └── main.rs
└── target
```
*add-one/src/lib.rs* 文件中,增加一个 `add_one` 函数:
<span class="filename">文件名: add-one/src/lib.rs</span>
```rust
pub fn add_one(x: i32) -> i32 {
x + 1
}
```
现在工作空间中有了一个库 crate`adder` 依赖库 crate `add-one`。首先需要在 *adder/Cargo.toml* 文件中增加 `add-one` 作为路径依赖:
<span class="filename">文件名: adder/Cargo.toml</span>
```toml
[dependencies]
add-one = { path = "add-one" }
add-one = { path = "../add-one" }
```
如果在 *Cargo.toml* 中增加依赖但没有指定对应 `path`,则这些将是不属于工作空间的假设来自于 Crates.io 的常规依赖。
工作空间中的 crate 不必相互依赖,所以仍需显示的表明工作空间中 crate 的依赖关系。
接下来,在 `adder` crate 中使用 `add-one` crate 的函数 `add_one`。打开 *adder/src/main.rs* 在顶部增加一行 `extern crate` 将新 `add-one` 库 crate 引入作用域。接着修改 `main` 函数来调用 `add_one` 函数,如示例 14-7 所示:
<span class="filename">文件名: adder/src/main.rs</span>
```rust,ignore
extern crate add_one;
fn main() {
let num = 10;
println!("Hello, world! {} plus one is {}!", num, add_one::add_one(num));
}
```
<span class="caption">示例 14-7`adder` crate 中使用 `add-one` 库 crate</span>
*add* 目录中运行 `cargo build` 来构建工作空间!
```text
$ cargo build
Compiling add-one v0.1.0 (file:///projects/add/add-one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.68 secs
```
为了在顶层 *add* 目录运行二进制 crate需要通过 `-p` 参数和包名称来运行 `cargo run` 指定工作空间中我们希望使用的包:
```text
$ cargo run -p adder
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
```
这会运行 *adder/src/main.rs* 中的代码,其依赖 `add-one` crate
### 在工作空间中创建第二个 crate
接下来,在 `adder` 目录中生成 `add-one` crate
@ -101,13 +209,9 @@ $ cargo build
└── target
```
工作空间在顶级目录有一个 *target* 目录;*add-one* 并没有自己的 *target* 目录。即使进入 `add-one` 目录运行 `cargo build`,构建结果也位于 *adder/target* 而不是 *adder/add-one/target*。因为工作空间中的 crate 之间相互依赖。如果每个 crate 有其自己的 *target* 目录,为了在自己的 *target* 目录中生成构建结果,工作空间中的每一个 crate 都不得不相互重新编译其他 crate。通过共享一个 *target* 目录,工作空间可以避免其他 crate 多余的重复构建。
#### 在工作空间中依赖外部 crate
还需注意的是工作空间只有一个 *Cargo.lock*,而不是拥有一个顶级的 *Cargo.lock* 和一个 *add-one/Cargo.lock*。这确保了所有的 crate 都使用完全相同版本的依赖。如果在 *Cargo.toml**add-one/Cargo.toml* 中都增加 `rand` crate则 Cargo 会将其都解析为同一版本并记录到唯一的 *Cargo.lock* 中。使得工作空间中的所有 crate 都使用相同的依赖意味着其中的 crate 都是相互减重的。现在就让我们来试一试。
让我们在 *add-one/Cargo.toml* 中的 `[dependencies]` 部分增加 `rand` crate 以便能够在 `add-one` crate 中使用 `rand` crate
还需注意的是工作空间只在根目录有一个 *Cargo.lock*,而不是在每一个 crate 目录都有 *Cargo.lock*。这确保了所有的 crate 都使用完全相同版本的依赖。如果在 *Cargo.toml**add-one/Cargo.toml* 中都增加 `rand` crate则 Cargo 会将其都解析为同一版本并记录到唯一的 *Cargo.lock* 中。使得工作空间中的所有 crate 都使用相同的依赖意味着其中的 crate 都是相互兼容的。让我们在 *add-one/Cargo.toml* 中的 `[dependencies]` 部分增加 `rand` crate 以便能够在 `add-one` crate 中使用 `rand` crate
<span class="filename">文件名: add-one/Cargo.toml</span>
@ -117,29 +221,29 @@ $ cargo build
rand = "0.3.14"
```
现在就可以在 *add-one/src/lib.rs* 中增加 `extern crate rand;` 了,接着在 *adder* 目录运行 `cargo build` 构建整个工作空间就会引入并编译 `rand` crate
现在就可以在 *add-one/src/lib.rs* 中增加 `extern crate rand;` 了,接着在 *add* 目录运行 `cargo build` 构建整个工作空间就会引入并编译 `rand` crate
```text
$ cargo build
Updating registry `https://github.com/rust-lang/crates.io-index`
Downloading rand v0.3.14
...snip...
--snip--
Compiling rand v0.3.14
Compiling add-one v0.1.0 (file:///projects/adder/add-one)
Compiling adder v0.1.0 (file:///projects/adder)
Compiling add-one v0.1.0 (file:///projects/add/add-one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 10.18 secs
```
现在顶级的 *Cargo.lock* 包含了 `add-one``rand` 依赖的信息。然而,即使 `rand` 被用于工作空间的某处,也不能在其他 crate 中使用它,除非也在他们的 *Cargo.toml* 中加入 `rand`。例如,如果在顶级的 `adder` crate 的 *src/main.rs* 中增加 `extern crate rand;`会得到一个错误:
现在顶级的 *Cargo.lock* 包含了 `add-one``rand` 依赖的信息。然而,即使 `rand` 被用于工作空间的某处,也不能在其他 crate 中使用它,除非也在他们的 *Cargo.toml* 中加入 `rand`。例如,如果在顶级的 `adder` crate 的 *adder/src/main.rs* 中增加 `extern crate rand;`,会得到一个错误:
```text
$ cargo build
Compiling adder v0.1.0 (file:///projects/adder)
error[E0463]: can't find crate for `rand`
--> src/main.rs:1:1
Compiling adder v0.1.0 (file:///projects/add/adder)
error: use of unstable library feature 'rand': use `rand` from crates.io (see
issue #27703)
--> adder/src/main.rs:1:1
|
1 | extern crate rand;
| ^^^^^^^^^^^^^^^^^^^ can't find crate
```
为了修复这个错误,修改顶级 `adder` crate 的 *Cargo.toml* 来表明 `rand` 也是这个 crate 的依赖。构建 `adder` crate 会将 `rand` 加入到 *Cargo.lock*`adder` 的依赖列表中,但是这并不会下载 `rand` 的额外拷贝。Cargo 确保了工作空间中任何使用 `rand` 的 crate 都采用相同的版本。在整个工作空间中使用相同版本的 `rand` 节省了空间,因为这样就无需多个拷贝并确保了工作空间中的 crate 将是相互兼容的。
@ -166,32 +270,21 @@ mod tests {
}
```
在顶级 *adder* 目录运行 `cargo test`
在顶级 *add* 目录运行 `cargo test`
```text
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Compiling add-one v0.1.0 (file:///projects/add/add-one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
Running target/debug/adder-f0253159197f7841
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
```
等等,零个测试?我们刚刚增加了一个测试!如果我们观察输出,就不难发现在工作空间中的 `cargo test` 只运行顶级 crate 的测试。为了运行工作空间中所有 crate 的测试,需要使用 `--all` 参数:
```text
$ cargo test --all
Finished dev [unoptimized + debuginfo] target(s) in 0.37 secs
Running target/debug/deps/add_one-abcabcabc
Running target/debug/deps/add_one-f0253159197f7841
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target/debug/deps/adder-abcabcabc
Running target/debug/deps/adder-f88af9d2cc175a5e
running 0 tests
@ -204,7 +297,9 @@ running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```
当传递了 `--all` 时,`cargo test` 会运行工作空间中所有 crate 的测试。也可以选择在顶级目录运行工作空间中特定 crate 的测试,通过使用 `-p` 参数并指定希望测试的 crate 的名称:
输出的第一部分显示 `add-one` crate 的 `it_works` 测试通过了。下一个部分显示 `adder` crate 中找到了 0 个测试,最后一部分显示 `add-one` crate 中有 0 个文档测试。在像这样的工作空间结构中运行 `cargo test` 会运行工作空间中所有 crate 的测试。
也可以选择运行工作空间中特定 crate 的测试,通过在根目录使用 `-p` 参数并指定希望测试的 crate 名称:
```text
$ cargo test -p add-one
@ -225,8 +320,8 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
输出显示了 `cargo test` 只运行了 `add-one` crate 的测试而没有运行 `adder` crate 的测试。
如果你选择向 crates.io 发布工作空间中的 crate每一个工作空间中的 crate 将会单独发布。`cargo publish` 命令并没有 `--all` 或者 `-p` 参数,所以必须进入每一个 crate 的目录并运行 `cargo publish` 来发布工作空间中的每一个 crate。
如果你选择向 *https://crates.io/* 发布工作空间中的 crate每一个工作空间中的 crate 将会单独发布。`cargo publish` 命令并没有 `--all` 或者 `-p` 参数,所以必须进入每一个 crate 的目录并运行 `cargo publish` 来发布工作空间中的每一个 crate。
现在尝试以类似 `add-one` crate 的方式向工作空间增加 `add-two` crate 来作为更多的练习!
随着项目增长,考虑使用工作空间:每一个更小的组件比一大块代码要容易理解。将 crate 保持在工作空间中易于协调他们的改变,如果他们一起运行并经常需要同时被修改的话。
随着项目增长,考虑使用工作空间:每一个更小的组件比一大块代码要容易理解。将 crate 保持在工作空间中易于协调他们的改变,如果他们一起运行并经常需要同时被修改的话。

BIN
src/img/trpl14-01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
src/img/trpl14-02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 42 KiB

View File

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB