wip: 2024 edition

pull/875/head
KaiserY 2 weeks ago
parent a136417b78
commit 30e92e8362

@ -130,15 +130,11 @@ let add_one_v4 = |x| x + 1 ;
<span class="caption">示例 13-6使用 `move` 来强制闭包为线程获取 `list` 的所有权</span>
我们生成了一个新的线程,并给这个线程传递一个闭包作为参数来运行,闭包体打印出列表。在示例 13-4 中,闭包仅通过不可变引用捕获了 `list`,因为这是打印列表所需的最少的访问权限。这个例子中,尽管闭包体依然只需要不可变引用,我们还是在闭包定义前写上 `move` 关键字,以确保 `list` 被移动到闭包中。新线程可能在主线程剩余部分执行完前执行完,也可能在主线程执行完之后执行完。如果主线程维护了 `list` 的所有权但却在新线程之前结束并且丢弃了 `list`,则在线程中的不可变引用将失效。因此,编译器要求 `list` 被移动到在新线程中运行的闭包中,这样引用就是有效的。试着移除 `move` 关键字,或者在闭包定义后在主线程中使用 `list`,看看你会得到什么编译器报错!
我们生成了一个新的线程,并给这个线程传递一个闭包作为参数来运行,闭包体打印出列表。在示例 13-4 中,闭包仅通过不可变引用捕获了 `list`,因为这是打印列表所需的最访问权限。这个例子中,尽管闭包体依然只需要不可变引用,我们还是在闭包定义前写上 `move` 关键字,以确保 `list` 被移动到闭包中。新线程可能在主线程剩余部分执行完前执行完,也可能在主线程执行完之后执行完。如果主线程维护了 `list` 的所有权但却在新线程之前结束并且丢弃了 `list`,则在线程中的不可变引用将失效。因此,编译器要求 `list` 被移动到在新线程中运行的闭包中,这样引用就是有效的。试着移除 `move` 关键字,或者在闭包定义后在主线程中使用 `list`,看看你会得到什么编译器报错!
<a id="storing-closures-using-generic-parameters-and-the-fn-traits"></a>
<a id="limitations-of-the-cacher-implementation"></a>
<a id="moving-captured-values-out-of-the-closure-and-the-fn-traits"></a>
### 将捕获的值移出闭包和 `Fn` trait
### 将被捕获的值移出闭包和 `Fn` trait
一旦闭包捕获了定义它的环境中的某个值的引用或所有权(也就影响了什么会被移 _进_ 闭包,如有),闭包体中的代码则决定了在稍后执行闭包时,这些引用或值将如何处理(也就影响了什么会被移 _出_ 闭包,如有)。闭包体可以执行以下任一操作:将一个捕获的值移出闭包,修改捕获的值,既不移动也不修改值,或者一开始就不从环境中捕获任何值。
一旦闭包捕获了定义它的环境中的某个值的引用或所有权(也就影响了什么会被移*进*闭包,如有),闭包体中的代码则决定了在稍后执行闭包时,这些引用或值将如何处理(也就影响了什么会被移*出*闭包,如有)。闭包体可以执行以下任一操作:将一个捕获的值移出闭包,修改捕获的值,既不移动也不修改值,或者一开始就不从环境中捕获任何值。
闭包捕获和处理环境中的值的方式会影响闭包实现哪些 trait而 trait 是函数和结构体指定它们可以使用哪些类型闭包的方式。根据闭包体如何处理这些值,闭包会自动、渐进地实现一个、两个或全部三个 `Fn` trait。
@ -166,15 +162,15 @@ impl<T> Option<T> {
接着注意到 `unwrap_or_else` 函数有额外的泛型参数 `F`。`F` 是参数 `f` 的类型,`f` 是调用 `unwrap_or_else` 时提供的闭包。
泛型 `F` 的 trait bound 是 `FnOnce() -> T`,这意味着 `F` 必须能够被调用一次,没有参数并返回一个 `T`。在 trait bound 中使用 `FnOnce` 表示 `unwrap_or_else` 最多只会调用 `f` 一次。在 `unwrap_or_else` 的函数体中可以看到,如果 `Option``Some``f` 不会被调用。如果 `Option``None``f` 将会被调用一次。由于所有的闭包都实现了 `FnOnce``unwrap_or_else` 接受所有三种类型的闭包,十分灵活。
泛型 `F` 的 trait bound 是 `FnOnce() -> T`,这意味着 `F` 必须能够被调用一次,没有参数并返回一个 `T`。在 trait bound 中使用 `FnOnce` 表示 `unwrap_or_else` 最多只会调用 `f` 一次。在 `unwrap_or_else` 的函数体中可以看到,如果 `Option``Some``f` 不会被调用。如果 `Option``None``f` 将会被调用一次。由于所有的闭包都实现了 `FnOnce``unwrap_or_else` 接受所有三种类型的闭包,灵活性达到极致
> 注意:函数也可以实现所有的三种 `Fn` traits。如果我们要做的事情不需要从环境中捕获值,则可以在需要某种实现了 `Fn` trait 的东西时使用函数而不是闭包。举个例子,可以在 `Option<Vec<T>>` 的值上调用 `unwrap_or_else(Vec::new)`,以便在值为 `None` 时获取一个新的空的 vector。
> 注意:如果我们要做的事情不需要从环境中捕获值,则可以在需要某种实现了 `Fn` trait 的东西时使用函数而不是闭包。举个例子,可以在 `Option<Vec<T>>` 的值上调用 `unwrap_or_else(Vec::new)`,以便在值为 `None` 时获取一个新的空的 vector。编译器会自动为函数定义实现适用的 `Fn` trait。
现在让我们来看定义在 slice 上的标准库方法 `sort_by_key`,看看它与 `unwrap_or_else` 的区别,以及为什么 `sort_by_key` 使用 `FnMut` 而不是 `FnOnce` 作为 trait bound。这个闭包以一个 slice 中当前被考虑的元素的引用作为参数,并返回一个可以排序的 `K` 类型的值。当你想按照 slice 中每个元素的某个属性进行排序时,这个函数非常有用。在示例 13-7 中,我们有一个 `Rectangle` 实例的列表,并使用 `sort_by_key``Rectangle``width` 属性对它们从低到高排序:
<span class="filename">文件名src/main.rs</span>
```rust,noplayground
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-07/src/main.rs}}
```
@ -196,7 +192,7 @@ impl<T> Option<T> {
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-08/src/main.rs}}
```
<span class="caption">示例 13-8尝试在 `sort_by_key` 上使用一个 `FnOnce` 闭包</span>
<span class="caption">示例 13-8尝试在 `sort_by_key` 上使用一个 `FnOnce` 闭包</span>
这是一个刻意构造的、复杂且无效的方式,试图统计在对 `list` 进行排序时 `sort_by_key` 调用闭包的次数。该代码试图通过将闭包环境中的 `value`(一个 `String`)插入 `sort_operations` vector 来实现计数。闭包捕获了 `value`,然后通过将 `value` 的所有权转移给 `sort_operations` vector 的方式将其移出闭包。这个闭包只能被调用一次;尝试第二次调用它将无法工作,因为这时 `value` 已经不在闭包的环境中,无法被再次插入 `sort_operations` 中!因而,这个闭包只实现了 `FnOnce`。当我们尝试编译此代码时,会出现错误提示:`value` 不能从闭包中移出,因为闭包必须实现 `FnMut`
@ -204,16 +200,16 @@ impl<T> Option<T> {
{{#include ../listings/ch13-functional-features/listing-13-08/output.txt}}
```
报错指向了闭包体中将 `value` 移出环境的那一行。要修复此问题,我们需要修改闭包体,使其不会将值移出环境。在环境中维护一个计数器,并在闭包体中递增其值,是计算闭包被调用次数的一个更简单直接的方法。示例 13-9 中的闭包可以在 `sort_by_key` 中使用,因为它只捕获了 `num_sort_operations` 计数器的可变引用,因此可以被多次调用:
报错指向了闭包体中将 `value` 移出环境的那一行。要修复此问题,我们需要修改闭包体,使其不会将值移出环境。在环境中维护一个计数器,并在闭包体中递增其值,是计算闭包被调用次数的一个更直观的方法。示例 13-9 中的闭包可以在 `sort_by_key` 中使用,因为它只捕获了 `num_sort_operations` 计数器的可变引用,因此可以被多次调用:
<span class="filename">文件名src/main.rs</span>
```rust,noplayground
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-09/src/main.rs}}
```
<span class="caption">示例 13-9允许在 `sort_by_key` 上使用一个 `FnMut` 闭包</span>
当定义或使用涉及闭包的函数或类型时,`Fn` traits 十分重要。在下个小节中,我们将讨论迭代器。许多迭代器方法都接收闭包参数,因此在继续前,请记住这些闭包的细节!
当定义或使用涉及闭包的函数或类型时,`Fn` trait 十分重要。在下个小节中,我们将讨论迭代器。许多迭代器方法都接收闭包参数,因此在继续前,请记住这些闭包的细节!
[unwrap-or-else]: https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else

@ -1,12 +1,11 @@
## 使用迭代器处理元素序列
> [ch13-02-iterators.md](https://github.com/rust-lang/book/blob/main/src/ch13-02-iterators.md)
> <br>
> commit eabaaaa90ee6937db3690dc56f739116be55ecb2
<!-- https://github.com/rust-lang/book/blob/main/src/ch13-02-iterators.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
迭代器模式允许你依次对一个序列中的项执行某些操作。**迭代器***iterator*)负责遍历序列中的每一项并确定序列何时结束的逻辑。使用迭代器时,你无需自己重新实现这些逻辑。
在 Rust 中,迭代器是 **惰性的***lazy*),这意味着在调用消费迭代器的方法之前不会执行任何操作。例如,示例 13-10 中的代码通过调用定义于 `Vec<T>` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身并没有执行任何有用的操作。
在 Rust 中,迭代器是**惰性的***lazy*),这意味着在调用消费迭代器的方法之前不会执行任何操作。例如,示例 13-10 中的代码通过调用定义于 `Vec<T>` 上的 `iter` 方法在一个 vector `v1` 上创建了一个迭代器。这段代码本身并没有执行任何有用的操作。
```rust
{{#rustdoc_include ../listings/ch13-functional-features/listing-13-10/src/main.rs:here}}
@ -24,13 +23,13 @@
<span class="caption">示例 13-11在一个 `for` 循环中使用迭代器</span>
在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 中的元素总量。
在标准库中没有提供迭代器的语言中,我们可能会使用一个从 0 开始索引变量,使用这个变量索引 vector 中的值,并循环增加其值直到达到 vector 中的元素总量,以实现相同的功能
迭代器为我们处理了所有这些逻辑,这减少了重复代码并消除了潜在的混乱。另外,迭代器的实现方式提供了对多种不同的序列使用相同逻辑的灵活性,而不仅仅是像 vector 这样可索引的数据结构。让我们看看迭代器是如何做到这些的。
### `Iterator` trait 和 `next` 方法
迭代器都实现了一个叫做 `Iterator` 的定义于标准库的 trait。这个 trait 的定义看起来像这样:
迭代器都实现了名为 `Iterator` 的定义于标准库的 trait。这个 trait 的定义看起来像这样:
```rust
pub trait Iterator {
@ -42,9 +41,9 @@ pub 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`
`Iterator` trait 仅要求实现者定义一方法:`next` 方法,该方法每次返回迭代器中的一个项,封装在 `Some` 中,并且当迭代完成时,返回 `None`
可以直接调用迭代器的 `next` 方法;示例 13-12 展示了对由 vector 创建的迭代器重复调用 `next` 方法时返回的值。
@ -56,7 +55,7 @@ pub trait Iterator {
<span class="caption">示例 13-12在迭代器上直接调用 `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`
@ -64,7 +63,7 @@ pub trait Iterator {
`Iterator` trait 有一系列不同的由标准库提供默认实现的方法;你可以在 `Iterator` trait 的标准库 API 文档中找到所有这些方法。一些方法在其定义中调用了 `next` 方法,这也就是为什么在实现 `Iterator` trait 时要求实现 `next` 方法的原因。
这些调用 `next` 方法的方法被称为 **消费适配器***consuming adaptors*),因为调用它们会消耗迭代器。一个消费适配器的例子是 `sum` 方法。这个方法获取迭代器的所有权并反复调用 `next` 来遍历迭代器,因而会消费迭代器。在遍历过程中,它将每个项累加到一个总和中,并在迭代完成时返回这个总和。示例 13-13 有一个展示 `sum` 方法使用的测试:
这些调用 `next` 方法的方法被称为**消费适配器***consuming adaptors*),因为调用它们会消耗迭代器。一个消费适配器的例子是 `sum` 方法,这个方法获取迭代器的所有权并反复调用 `next` 来遍历迭代器,从而消费迭代器。在遍历过程中,它将每个项累加到一个运行时总和中,并在迭代完成时返回这个总和。示例 13-13 有一个展示 `sum` 方法使用的测试:
<span class="filename">文件名src/lib.rs</span>
@ -78,7 +77,7 @@ pub trait Iterator {
### 产生其他迭代器的方法
`Iterator` trait 中定义了另一类方法,被称为 **迭代器适配器***iterator adaptors*),它们不会消耗当前的迭代器,而是通过改变原始迭代器的某些方面来生成不同的迭代器。
`Iterator` trait 中定义了另一类方法,被称为**迭代器适配器***iterator adaptors*),它们不会消耗当前的迭代器,而是通过改变原始迭代器的某些方面来生成不同的迭代器。
示例 13-14 展示了一个调用迭代器适配器方法 `map` 的例子,该方法使用一个闭包对每个元素进行操作。`map` 方法返回一个新的迭代器,该迭代器生成经过修改的元素。这里的闭包创建了一个新的迭代器,其中 vector 中的每个元素都被加 1。

@ -1,12 +1,11 @@
## 改进 I/O 项目
> [ch13-03-improving-our-io-project.md](https://github.com/rust-lang/book/blob/main/src/ch13-03-improving-our-io-project.md)
> <br>
> commit 2cd1b5593d26dc6a03c20f8619187ad4b2485552
<!-- https://github.com/rust-lang/book/blob/main/src/ch13-03-improving-our-io-project.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
掌握了这些关于迭代器的新知识后,我们可以使用迭代器来改进第十二章中 I/O 项目的实现来使得代码更简洁明了。接下来,让我们看看迭代器如何改进 `Config::build` 函数和 `search` 函数的实现。
### 使用迭代器`clone`
### 使用迭代器`clone`
在示例 12-6 中,我们增加了一些代码获取一个 `String` 类型的 slice 并创建一个 `Config` 结构体的实例,它们索引 slice 中的值并克隆这些值以便 `Config` 结构体可以拥有这些值。在示例 13-17 中重现了第十二章结尾示例 12-23 中 `Config::build` 函数的实现:
@ -20,7 +19,7 @@
当时我们说过不必担心低效的 `clone` 调用,因为我们以后会将其移除。好吧,就是现在!
起初这里需要 `clone` 的原因是参数 `args` 中有一个 `String` 元素的 slice`build` 函数并不拥有 `args`。为了能够返回 `Config` 实例的所有权,我们需要克隆 `Config` 中字段 `query``file_path` 的值,这样 `Config` 实例就能拥有这些值。
起初这里需要 `clone` 的原因是参数 `args` 中有一个 `String` 元素的 slice`build` 函数并不拥有 `args`。为了能够返回 `Config` 实例的所有权,我们不得不克隆 `Config` 中字段 `query``file_path` 的值,这样 `Config` 实例就能拥有这些值。
在学习了迭代器之后,我们可以将 `build` 函数改为获取一个有所有权的迭代器作为参数,而不是借用 slice。我们将使用迭代器功能代替之前检查 slice 长度和索引特定位置的代码。这样可以更清晰地表达 `Config::build` 函数的操作,因为迭代器会负责访问这些值。
@ -60,11 +59,11 @@
`env::args` 函数的标准库文档显示,它返回的迭代器的类型为 `std::env::Args`,并且这个类型实现了 `Iterator` trait 并返回 `String` 值。
我们已经更新了 `Config::build` 函数的签名,因此参数 `args` 有一个带有 trait bounds `impl Iterator<Item = String>` 的泛型类型,而不是 `&[String]`。这里用到了第十章 [“trait 作为参数”][impl-trait] 部分讨论过的 `impl Trait` 语法,这意味着 `args` 可以是任何实现了 `Iterator` trait 并返回 `String`item的类型。
我们已经更新了 `Config::build` 函数的签名,因此参数 `args` 有一个带有 trait bound `impl Iterator<Item = String>` 的泛型类型,而不是 `&[String]`。这里用到了第十章[“trait 作为参数”][impl-trait]部分讨论过的 `impl Trait` 语法,这意味着 `args` 可以是任何实现了 `Iterator` trait 并返回 `String`item的类型。
由于我们获取了 `args` 的所有权,并且将通过迭代来修改 `args`,因此我们可以在 `args` 参数的声明中添加 `mut` 关键字,使其可变。
#### 使用 `Iterator` trait 代替索引
#### 使用 `Iterator` trait 方法代替索引
接下来,我们将修改 `Config::build` 的函数体。因为 `args` 实现了 `Iterator` trait因此我们知道可以对其调用 `next` 方法!示例 13-20 更新了示例 12-23 中的代码,以使用 `next` 方法:
@ -78,7 +77,7 @@
请记住 `env::args` 返回值的第一个值是程序的名称。我们希望忽略它并获取下一个值,所以首先调用 `next` 且不对其返回值做任何操作。然后,我们再次调用 `next` 来获取要放入 `Config` 结构体的 `query` 字段的值。如果 `next` 返回 `Some`,使用 `match` 来提取其值。如果它返回 `None`,则意味着没有提供足够的参数并通过 `Err` 值提早返回。我们对 `file_path` 的值也进行同样的操作。
### 使用迭代器适配器来使代码更简明
### 使用迭代器适配器让代码更清晰
I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13-21 中重现了第十二章结尾示例 12-19 中此函数的定义:
@ -90,7 +89,7 @@ I/O 项目中其他可以利用迭代器的地方是 `search` 函数,示例 13
<span class="caption">示例 13-21示例 12-19 中 `search` 函数的定义</span>
可以通过使用迭代器适配器方法来编写更简明的代码。这样做还可以避免使用一个可变的中间 `results` vector。函数式编程风格倾向于最小化可变状态的数量来使代码更简洁。去除可变状态可能会使未来的并行搜索优化变得更容易,因为我们不必管理对 `results` vector 的并发访问。示例 13-22 展示了这一变化:
可以通过使用迭代器适配器方法来编写更简明的代码。这样做还可以避免使用一个可变的中间 `results` vector。函数式编程风格倾向于最小化可变状态的数量来使代码更清晰。去除可变状态可能会使未来的并行搜索优化变得更容易,因为我们不必管理对 `results` vector 的并发访问。示例 13-22 展示了这一变化:
<span class="filename">文件名src/lib.rs</span>

@ -1,29 +1,25 @@
## 性能对比:循环 VS 迭代器
> [ch13-04-performance.md](https://github.com/rust-lang/book/blob/main/src/ch13-04-performance.md)
> <br>
> commit 009fffa4580ffb175f1b8470b5b12e4a63d670e4
<!-- https://github.com/rust-lang/book/blob/main/src/ch13-04-performance.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
为了决定是否使用循环或迭代器,你需要了解哪个实现更快:使用显式 `for` 循环的 `search` 函数版本,还是使用迭代器的版本。
我们进行了一个基准测试,将阿瑟·柯南·道尔的《福尔摩斯探案集》的全部内容加载到一个 `String` 中,并在内容中查找单词 “the”。以下是使用 `for` 循环版本和使用迭代器版本的 `search` 函数的基准测试结果:
我们进行了一个基准测试,将阿瑟·柯南·道尔的《福尔摩斯探案集》全文加载到一个 `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)
```
结果迭代器版本还要稍微快一点!这里我们不会解释性能测试的代码,我们的目的并不是为了证明它们是完全等同的,而是得出一个怎样比较这两种实现方式性能的基本思路。
两种实现具有相似的性能表现!这里我们不会解释性能测试的代码,我们的目的并不是为了证明它们是完全等同的,而是得出一个怎样比较这两种实现方式性能的基本思路。
对于一个更全面的性能测试,你应该使用不同大小的文本作为 `contents`,不同的单词以及长度各异的单词作为 `query`,以及各种其他变化进行检查。关键在于:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能的代码。迭代器是 Rust 的 **零成本抽象***zero-cost abstractions*之一它意味着抽象并不会引入额外的运行时开销它与本贾尼·斯特劳斯特卢普C++ 的设计和实现者)在 “Foundations of C++”2012中所定义的 **零开销***zero-overhead*)如出一辙:
对于一个更全面的性能测试,你应该使用不同大小的文本作为 `contents`,不同的单词以及长度各异的单词作为 `query`,以及各种其他变化进行检查。关键在于:迭代器,作为一个高级的抽象,被编译成了与手写的底层代码大体一致性能的代码。迭代器是 Rust 的**零成本抽象***zero-cost abstractions*之一它意味着抽象并不会引入额外的运行时开销它与本贾尼·斯特劳斯特卢普C++ 的设计和实现者)在 《Foundations of C++》2012中所定义的**零开销***zero-overhead*)如出一辙:
> In general, C++ implementations obey the zero-overhead principle: What you don't use, you don't pay for. And further: What you do use, you couldn't hand code any better.
>
> - Bjarne Stroustrup "Foundations of C++"
>
> 从整体来说C++ 的实现遵循了零开销原则:你不需要的,无需为它买单。更有甚者的是:你需要的时候,也无法通过手写代码做得更好。
>
> - 本贾尼·斯特劳斯特卢普 "Foundations of C++"
> 总的来说C++ 的实现遵循了零开销原则:不使用的功能无需为其付出代价;而已经使用的功能,也不可能通过手写代码做得更好。
作为另一个例子以下代码取自一个音频解码器。解码算法使用线性预测数学运算linear prediction mathematical operation来根据之前样本的线性函数预测将来的值。这些代码使用迭代器链对作用域中的三个变量进行某种数学计算一个叫 `buffer` 的数据 slice、一个有 12 个元素的数组 `coefficients`、和一个代表位数据位移量的 `qlp_shift`。我们在这个例子中声明了这些变量,但没有为它们赋值;虽然这些代码在其上下文之外没有太多意义,不过仍是一个简明的现实例子,来展示 Rust 如何将高级概念转换为底层代码。
@ -46,10 +42,10 @@ for i in 12..buffer.len() {
像音频解码器这样的程序通常最看重计算的性能。这里,我们创建了一个迭代器,使用了两个适配器,接着消费了其值。那么这段 Rust 代码将会被编译为什么样的汇编代码呢?好吧,在编写本书的这个时候,它被编译成与手写的相同的汇编代码。遍历 `coefficients` 的值完全用不到循环Rust 知道这里会迭代 12 次所以它“展开”unroll了循环。展开是一种将循环迭代转换为重复代码并移除循环控制代码开销的代码优化技术。
所有的系数都被储存在了寄存器中,这意味着访问它们非常快。这里也没有运行时数组访问边界检查。所有这些 Rust 能够提供的优化使得结果代码极为高效。现在你知道了这些,请放心大胆的使用迭代器和闭包吧!它们使得代码看起来更高级,但并不为此引入运行时性能损失。
所有的系数coefficients都被储存在了寄存器中,这意味着访问它们非常快。这里也没有运行时数组访问边界检查。所有这些 Rust 能够提供的优化使得结果代码极为高效。现在你知道了这些,请放心大胆的使用迭代器和闭包吧!它们使得代码看起来更高级,但并不为此引入运行时性能损失。
## 总结
闭包和迭代器是 Rust 受函数式编程语言观念所启发的功能。它们对 Rust 以高性能来明确的表达高级概念的能力有很大贡献。闭包和迭代器的实现达到了不影响运行时性能的程度。这正是 Rust 致力于提供零成本抽象的目标的一部分。
闭包和迭代器是 Rust 受函数式编程语言观念所启发的特性。它们对 Rust 以高性能来明确的表达高级概念的能力有很大贡献。闭包和迭代器的实现达到了不影响运行时性能的程度。这正是 Rust 致力于提供零成本抽象的目标的一部分。
现在我们改进了 I/O 项目的(代码)表现力,那么让我们来看看 `cargo` 的更多功能,这些功能将帮助我们将项目分享给全世界。

@ -1,15 +1,14 @@
# 进一步认识 Cargo 和 Crates.io
> [ch14-00-more-about-cargo.md](https://github.com/rust-lang/book/blob/main/src/ch14-00-more-about-cargo.md)
> <br>
> commit 44e31f9f304e0cd9ace01045d17a2aa01a449528
<!-- https://github.com/rust-lang/book/blob/main/src/ch14-00-more-about-cargo.md -->
<!-- commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -->
目前为止我们只使用过 Cargo 构建、运行和测试代码这些最基本的功能,不过它还可以做到更多。本章会讨论 Cargo 其他一些更为高级的功能,我们将展示如何:
* 使用发布配置来自定义构建
* 将库发布到 [crates.io](https://crates.io)
* 使用工作空间来组织更大的项目
* 从 [crates.io](https://crates.io) 安装二进制文件
* 使用自定义的命令来扩展 Cargo
- 使用发布配置release profiles来自定义构建
- 将库发布到 [crates.io](https://crates.io)
- 使用工作空间workspaces来组织更大的项目
- 从 [crates.io](https://crates.io) 安装二进制文件
- 使用自定义的命令来扩展 Cargo
Cargo 的功能不止本章所介绍的,关于其全部功能的详尽解释,请查看 [文档](http://doc.rust-lang.org/cargo/)
Cargo 的功能不止本章所介绍的,关于其全部功能的详尽解释,请查看[文档](http://doc.rust-lang.org/cargo/)

@ -1,10 +1,9 @@
## 采用发布配置自定义构建
> [ch14-01-release-profiles.md](https://github.com/rust-lang/book/blob/main/src/ch14-01-release-profiles.md)
> <br>
> commit 44e31f9f304e0cd9ace01045d17a2aa01a449528
<!-- https://github.com/rust-lang/book/blob/main/src/ch14-01-release-profiles.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
在 Rust 中 **发布配置***release profiles*)文件是预定义和可定制的,它们包含不同的配置,允许程序员更灵活地控制代码编译的多种选项。每一个配置都相互独立。
在 Rust 中**发布配置***release profiles*)文件是预定义和可定制的,它们包含不同的配置,允许程序员更灵活地控制代码编译的多种选项。每一个配置都相互独立。
Cargo 有两个主要的配置:运行 `cargo build` 时采用的 `dev` 配置和运行 `cargo build --release``release` 配置。`dev` 配置为开发定义了良好的默认配置,`release` 配置则为发布构建定义了良好的默认配置。
@ -12,9 +11,9 @@ Cargo 有两个主要的配置:运行 `cargo build` 时采用的 `dev` 配置
```console
$ cargo build
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
$ cargo build --release
Finished release [optimized] target(s) in 0.0s
Finished `release` profile [optimized] target(s) in 0.32s
```
构建输出中的 `dev``release` 表明编译器在使用不同的配置。
@ -44,4 +43,4 @@ opt-level = 1
这会覆盖默认的设置 `0`。现在运行 `cargo build`Cargo 将会使用 `dev` 的默认配置加上定制的 `opt-level`。因为 `opt-level` 设置为 `1`Cargo 会比默认进行更多的优化,但是没有发布构建那么多。
对于每个配置的设置和其默认值的完整列表,请查看 [Cargo 的文档](https://doc.rust-lang.org/cargo/reference/profiles.html)。
对于每个配置的设置和其默认值的完整列表,请参阅[Cargo 的文档](https://doc.rust-lang.org/cargo/reference/profiles.html)。

@ -1,17 +1,17 @@
## 将 crate 发布到 Crates.io
> [ch14-02-publishing-to-crates-io.md](https://github.com/rust-lang/book/blob/main/src/ch14-02-publishing-to-crates-io.md) <br>
> commit 3f2a6ef48943ade3e9c0eb23d69e2b8b41f057f1
<!-- https://github.com/rust-lang/book/blob/main/src/ch14-02-publishing-to-crates-io.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
我们曾经在项目中使用 [crates.io](https://crates.io)<!-- ignore --> 上的包作为依赖,不过你也可以通过发布自己的包来向他人分享代码。[crates.io](https://crates.io)<!-- ignore --> 用来分发包的源代码,所以它主要托管开源代码。
我们曾经在项目中使用 [crates.io](https://crates.io)<!-- ignore --> 上的包作为依赖,不过你也可以通过发布自己的包来向他人分享代码。[crates.io](https://crates.io)<!-- ignore --> 上的 crate 注册表会分发你包的源代码,因此它主要托管开源代码。
Rust 和 Cargo 有一些帮助他人更方便地找到和使用你发布的包的功能。我们将介绍一些这样的功能,接着讲到如何发布一个包。
### 编写有用的文档注释
准确的包文档有助于其他用户理解如何以及何时使用它们,所以花一些时间编写文档是值得的。第三章中我们讨论了如何使用双斜杠 `//` 注释 Rust 代码。Rust 也有特定的用于文档的注释类型,通常被称为 **文档注释**_documentation comments_),它们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,它们意在让对库感兴趣的程序员理解如何 **使用** 这个 crate而不是它是如何被 **实现** 的。
准确的包文档有助于其他用户理解如何以及何时使用它们,所以花一些时间编写文档是值得的。第三章中我们讨论了如何使用双斜杠 `//` 注释 Rust 代码。Rust 也有特定的用于文档的注释类型,通常被称为**文档注释***documentation comments*),它们会生成 HTML 文档。这些 HTML 展示公有 API 文档注释的内容,它们意在让对库感兴趣的程序员理解如何**使用**这个 crate而不是它是如何被**实现**的。
文档注释使用三斜杠 `///` 而不是双斜杠以支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。示例 14-1 展示了一个 `my_crate` crate 中 `add_one` 函数的文档注释
文档注释使用三斜杠 `///` 而不是双斜杠以支持 Markdown 注解来格式化文本。文档注释就位于需要文档的项的之前。示例 14-1 展示了一个 `my_crate` crate 中 `add_one` 函数的文档注释
<span class="filename">文件名src/lib.rs</span>
@ -21,11 +21,11 @@ Rust 和 Cargo 有一些帮助他人更方便地找到和使用你发布的包
<span class="caption">示例 14-1一个函数的文档注释</span>
这里,我们提供了一个 `add_one` 函数工作的描述,接着开始了一个标题为 `Examples` 的部分,和展示如何使用 `add_one` 函数的代码。可以运行 `cargo doc` 来生成这个文档注释的 HTML 文档。这个命令运行由 Rust 分发的工具 `rustdoc` 并将生成的 HTML 文档放入 _target/doc_ 目录。
这里,我们提供了一个 `add_one` 函数工作的描述,接着开始了一个标题为 `Examples` 的部分,和展示如何使用 `add_one` 函数的代码。可以运行 `cargo doc` 来生成这个文档注释的 HTML 文档。这个命令运行由 Rust 分发的工具 `rustdoc` 并将生成的 HTML 文档放入 *target/doc* 目录。
为了方便起见,运行 `cargo doc --open` 会构建当前 crate 文档(同时还有所有 crate 依赖的文档)的 HTML 并在浏览器中打开。导航到 `add_one` 函数将会发现文档注释的文本是如何渲染的,如图 14-1 所示:
<img alt="`my_crate` 的 `add_one` 函数所渲染的文档注释 HTML" src="img/trpl14-01.png" class="center" />
<img alt="Rendered HTML documentation for the `add_one` function of `my_crate`" src="img/trpl14-01.png" class="center" />
<span class="caption">图 14-1`add_one` 函数的文档注释 HTML</span>
@ -68,25 +68,25 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
<span class="caption">示例 14-2`my_crate` crate 整体的文档</span>
注意 `//!` 的最后一行之后没有任何代码。因为它们以 `//!` 开头而不是 `///`,这是属于包含此注释的项而不是注释之后项的文档。在这个情况下_src/lib.rs_ 文件,也就是 crate 根文件。这些注释描述了整个 crate。
注意 `//!` 的最后一行之后没有任何代码。因为它们以 `//!` 开头而不是 `///`,这是属于包含此注释的项而不是注释之后项的文档。在这个情况下*src/lib.rs* 文件,也就是 crate 根文件。这些注释描述了整个 crate。
如果运行 `cargo doc --open`,将会发现这些注释显示在 `my_crate` 文档的首页,位于 crate 中公有项列表之上,如图 14-2 所示:
<img alt="crate 整体注释所渲染的 HTML 文档" src="img/trpl14-02.png" class="center" />
<img alt="Rendered HTML documentation with a comment for the crate as a whole" src="img/trpl14-02.png" class="center" />
<span class="caption">图 14-2包含 `my_crate` 整体描述的注释所渲染的文档</span>
位于项之中的文档注释对于描述 crate 和模块特别有用。使用它们描述其容器整体的目的来帮助 crate 用户理解你的代码组织。
### 使用 `pub use` 导出合适的公有 API
### 使用 `pub use` 导出便捷的公有 API
公有 API 的结构是你发布 crate 时主要需要考虑的。crate 用户没有你那么熟悉其结构,并且如果模块层级过大他们可能会难以找到所需的部分。
第七章介绍了如何使用 `mod` 关键字来将代码组织进模块中,如何使用 `pub` 关键字将项变为公有,和如何使用 `use` 关键字将项引入作用域。然而你开发时候使用的文件架构可能并不方便用户。你的结构可能是一个包含多个层级的分层结构,不过这对于用户来说并不方便。这是因为想要使用被定义在很深层级中的类型的人可能很难发现这些类型的存在。他们也可能会厌烦要使用 `use my_crate::some_module::another_module::UsefulType;` 而不是 `use my_crate::UsefulType;` 来使用类型。
第七章介绍了如何使用 `pub` 关键字将项变为公有,和如何使用 `use` 关键字将项引入作用域。然而你开发时候使用的文件架构可能并不方便用户使用。你的结构可能是一个包含多个层级的分层结构,不过这对于用户来说并不方便。这是因为想要使用被定义在很深层级中的类型的人可能很难发现这些类型的存在。他们也可能会厌烦要使用 `use my_crate::some_module::another_module::UsefulType;` 而不是 `use my_crate::UsefulType;` 来使用类型。
好消息是,即使文件结构对于用户来说 **不是** 很方便,你也无需重新安排内部组织:你可以选择使用 `pub use` 重导出re-export项来使公有结构不同于私有结构。重导出获取位于一个位置的公有项并将其公开到另一个位置好像它就定义在这个新位置一样。
好消息是,即使文件结构对于用户来说**不是**很方便,你也无需重新安排内部组织:你可以选择使用 `pub use` 重导出re-export项来使公有结构不同于私有结构。重导出获取位于一个位置的公有项并将其公开到另一个位置好像它就定义在这个新位置一样。
例如,假设我们创建了一个描述美术信息的库 `art`。这个库中包含了一个有两个枚举 `PrimaryColor``SecondaryColor` 的模块 `kinds`,以及一个包含函数 `mix` 的模块 `utils`,如示例 14-3 所示:
例如,假设我们创建了一个描述艺术概念的库 `art`。这个库中包含了一个有两个枚举 `PrimaryColor``SecondaryColor` 的模块 `kinds`,以及一个包含函数 `mix` 的模块 `utils`,如示例 14-3 所示:
<span class="filename">文件名src/lib.rs</span>
@ -98,7 +98,7 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
`cargo doc` 所生成的 crate 文档首页如图 14-3 所示:
<img alt="包含 `kinds``utils` 模块的 `art`" src="img/trpl14-03.png" class="center" />
<img alt="Rendered documentation for the `art` crate that lists the `kinds` and `utils` modules" src="img/trpl14-03.png" class="center" />
<span class="caption">图 14-3包含 `kinds``utils` 模块的库 `art` 的文档首页</span>
@ -126,13 +126,13 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
<span class="caption">示例 14-5增加 `pub use` 语句重导出项</span>
现在此 crate 由 `cargo doc` 生成的 API 文档会在首页列出重导出的项以及其链接,如图 14-4 所示,这使得 `PrimaryColor``SecondaryColor` 类型和 `mix` 函数更易于查找。
现在此 crate 由 `cargo doc` 生成的 API 文档会在首页列出重导出的项以及其链接re-exports,如图 14-4 所示,这使得 `PrimaryColor``SecondaryColor` 类型和 `mix` 函数更易于查找。
<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-4 中的内部结构,或者可以使用示例 14-5 中更为方便的结构,如示例 14-6 所示:
`art` crate 的用户仍然可以看到并使用示例 14-3 中的内部结构,如示例 14-4 所示,或者可以使用示例 14-5 中更为方便的结构,如示例 14-6 所示:
<span class="filename">文件名src/main.rs</span>
@ -148,19 +148,20 @@ test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
### 创建 Crates.io 账号
在你可以发布任何 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` 命令,像这样
在你可以发布任何 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。然后运行 `cargo login` 命令,并在提示时粘贴该 token操作如下所示
```console
$ cargo login abcdefghijklmnopqrstuvwxyz012345
$ cargo login
abcdefghijklmnopqrstuvwxyz012345
```
这个命令会通知 Cargo 你的 API token 并将其储存在本地的 _~/.cargo/credentials_ 文件中。注意这个 token 是一个 **秘密****secret**)且不应该与其他人共享。如果因为任何原因与他人共享了这个信息,应该立即到 [crates.io](https://crates.io)<!-- ignore --> 撤销并重新生成一个 token。
这个命令会通知 Cargo 你的 API token 并将其储存在本地的 *~/.cargo/credentials* 文件中。注意这个 token 是一个**秘密****secret**)且不应该与其他人共享。如果因为任何原因与他人共享了这个信息,应该立即到 [crates.io](https://crates.io)<!-- ignore --> 撤销并重新生成一个 token。
### 向新 crate 添加元信息
### 向新 crate 添加元数据
比如说你已经有一个希望发布的 crate。在发布之前你需要在 crate 的 _Cargo.toml_ 文件的 `[package]` 部分增加一些本 crate 的元信息metadata
比如说你已经有一个希望发布的 crate。在发布之前你需要在 crate 的 *Cargo.toml* 文件的 `[package]` 部分增加一些本 crate 的元数据metadata
首先 crate 需要一个唯一的名称。虽然在本地开发 crate 时,可以使用任何你喜欢的名称。不过 [crates.io](https://crates.io)<!-- ignore --> 上的 crate 名称遵守先到先得的分配原则。一旦某个 crate 名称被使用,其他人就不能再发布这个名称的 crate 了。请搜索你希望使用的名称来找出它是否已被使用。如果没有,修改 _Cargo.toml_`[package]` 里的名称为你希望用于发布的名称,像这样:
首先 crate 需要一个唯一的名称。虽然在本地开发 crate 时,可以使用任何你喜欢的名称。不过 [crates.io](https://crates.io)<!-- ignore --> 上的 crate 名称遵守先到先得的分配原则。一旦某个 crate 名称被使用,其他人就不能再发布这个名称的 crate 了。请搜索你希望使用的名称来找出它是否已被使用。如果没有,修改 _Cargo.toml_`[package]` 里的`name` 字段为你希望用于发布的名称,像这样:
<span class="filename">文件名Cargo.toml</span>
@ -180,10 +181,10 @@ See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for
error: failed to publish to registry at https://crates.io
Caused by:
the remote server responded with an error: missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for how to upload metadata
the remote server responded with an error (status 400 Bad Request): missing or empty metadata fields: description, license. Please see https://doc.rust-lang.org/cargo/reference/manifest.html for more information on configuring these fields
```
这个错误是因为我们缺少一些关键信息:关于该 crate 用途的描述和用户可能在何种条款下使用该 crate 的 license。在 _Cargo.toml_ 中添加通常是一两句话的描述,因为它将在搜索结果中和你的 crate 一起显示。对于 `license` 字段,你需要一个 **license 标识符值**_license identifier value_)。[Linux 基金会的 Software Package Data Exchange (SPDX)][spdx] 列出了可以使用的标识符。例如,为了指定 crate 使用 MIT License增加 `MIT` 标识符:
这个错误是因为我们缺少一些关键信息:关于该 crate 用途的描述和用户可能在何种条款下使用该 crate 的 license。在 _Cargo.toml_ 中添加通常是一两句话的描述,因为它将在搜索结果中和你的 crate 一起显示。对于 `license` 字段,你需要一个 **license 标识符值***license identifier value*)。[Linux 基金会的 Software Package Data Exchange (SPDX)][spdx] 列出了可以使用的标识符。例如,为了指定 crate 使用 MIT License增加 `MIT` 标识符:
<span class="filename">文件名Cargo.toml</span>
@ -197,7 +198,7 @@ license = "MIT"
关于项目所适用的 license 指导超出了本书的范畴。很多 Rust 社区成员选择与 Rust 自身相同的 license这是一个双许可的 `MIT OR Apache-2.0`。这个实践展示了也可以通过 `OR` 分隔为项目指定多个 license 标识符。
那么,有了唯一的名称、版本号、由 `cargo new` 新建项目时增加的作者信息、描述和所选择的 license已经准备好发布的项目的 _Cargo.toml_ 文件可能看起来像这样:
那么,有了唯一的名称、版本号、由 `cargo new` 新建项目时增加的作者信息、描述和所选择的 license已经准备好发布的项目的 *Cargo.toml* 文件可能看起来像这样:
<span class="filename">文件名Cargo.toml</span>
@ -205,20 +206,20 @@ license = "MIT"
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
edition = "2024"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"
[dependencies]
```
[Cargo 的文档](http://doc.rust-lang.org/cargo/) 描述了其他可以指定的元信息,它们可以帮助你的 crate 更容易被发现和使用!
[Cargo 的文档](http://doc.rust-lang.org/cargo/) 描述了其他可以指定的元数据,它们可以帮助你的 crate 更容易被发现和使用!
### 发布到 Crates.io
现在我们创建了一个账号,保存了 API token为 crate 选择了一个名字,并指定了所需的元数据,你已经准备好发布了!发布 crate 会上传特定版本的 crate 到 [crates.io](https://crates.io)<!-- ignore --> 以供他人使用。
发布 crate 时请多加小心,因为发布是 **永久性的**_permanent_。对应版本不可能被覆盖其代码也不可能被删除。[crates.io](https://crates.io)<!-- ignore --> 的一个主要目标是作为一个存储代码的永久文档服务器,这样所有依赖 [crates.io](https://crates.io)<!-- ignore --> 中的 crate 的项目都能一直正常工作。而允许删除版本没办法达成这个目标。然而,可以被发布的版本号却没有限制。
发布 crate 时请多加小心,因为发布是**永久性的**_permanent_。对应版本不可能被覆盖其代码也不可能被删除。[crates.io](https://crates.io)<!-- ignore --> 的一个主要目标是作为一个存储代码的永久文档服务器,这样所有依赖 [crates.io](https://crates.io)<!-- ignore --> 中的 crate 的项目都能一直正常工作。而允许删除版本没办法达成这个目标。然而,可以被发布的版本号却没有限制。
再次运行 `cargo publish` 命令。这次它应该会成功:
@ -229,21 +230,21 @@ $ cargo publish
Verifying guessing_game v0.1.0 (file:///projects/guessing_game)
Compiling guessing_game v0.1.0
(file:///projects/guessing_game/target/package/guessing_game-0.1.0)
Finished dev [unoptimized + debuginfo] target(s) in 0.19s
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s
Uploading guessing_game v0.1.0 (file:///projects/guessing_game)
```
恭喜!你现在向 Rust 社区分享了代码,而且任何人都可以轻松的将你的 crate 加入他们项目的依赖。
### 发布现 crate 的新版本
### 发布现 crate 的新版本
当你修改了 crate 并准备好发布新版本时,改变 _Cargo.toml_`version` 所指定的值。请使用 [语义化版本规则][semver] 来根据修改的类型决定下一个版本号。接着运行 `cargo publish` 来上传新版本。
当你修改了 crate 并准备好发布新版本时,改变 *Cargo.toml*`version` 所指定的值。请使用[语义化版本规则][semver]来根据修改的类型决定下一个版本号。接着运行 `cargo publish` 来上传新版本。
### 使用 `cargo yank` 从 Crates.io 弃用版本
### 使用 `cargo yank` 从 Crates.io 撤回版本
虽然你不能删除之前版本的 crate但是可以阻止任何将来的项目将它们加入到依赖中。这在某个版本因为这样或那样的原因被破坏的情况很有用。对于这种情况Cargo 支持 **撤回**_yanking_)某个版本。
虽然你不能删除 crate 的历史版本但是可以阻止任何将来的项目将它们加入到依赖中。这在某个版本因为这样或那样的原因被破坏的情况很有用。对于这种情况Cargo 支持**撤回***yanking*)某个版本。
撤回某个版本会阻止新项目依赖此版本,不过所有现存此依赖的项目仍然能够下载和依赖这个版本。从本质上说,撤回意味着所有带有 _Cargo.lock_ 的项目的依赖不会被破坏,同时任何新生成的 _Cargo.lock_ 将不能使用被撤回的版本。
**撤回**某个版本会阻止新项目依赖此版本,不过所有现存此依赖的项目仍然能够下载和依赖这个版本。从本质上说,撤回意味着所有带有 *Cargo.lock* 的项目的依赖不会被破坏,同时任何新生成的 *Cargo.lock* 将不能使用被撤回的版本。
为了撤回一个版本的 crate在之前发布 crate 的目录运行 `cargo yank` 并指定希望撤回的版本。例如,如果我们发布了一个名为 `guessing_game` 的 crate 的 1.0.1 版本并希望撤回它,在 `guessing_game` 项目目录运行:
@ -261,7 +262,7 @@ $ cargo yank --vers 1.0.1 --undo
Unyank guessing_game@1.0.1
```
撤回 **并没有** 删除任何代码。举例来说,撤回功能并不能删除不小心上传的秘密信息。如果出现了这种情况,请立即重新设置这些秘密信息。
撤回**并没有**删除任何代码。举例来说,撤回功能并不能删除不小心上传的秘密信息。如果出现了这种情况,请立即重新设置这些秘密信息。
[spdx]: http://spdx.org/licenses/
[semver]: http://semver.org/

@ -1,21 +1,20 @@
## Cargo 工作空间
> [ch14-03-cargo-workspaces.md](https://github.com/rust-lang/book/blob/main/src/ch14-03-cargo-workspaces.md)
> <br>
> commit 704c51eec2f26a0133ae17a2c01986590c05a045
<!-- https://github.com/rust-lang/book/blob/main/src/ch14-03-cargo-workspaces.md -->
<!-- commit 56ec353290429e6547109e88afea4de027b0f1a9 -->
第十二章中,我们构建一个包含二进制 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 将会是相同工作空间的一部分。让我们以新建工作空间目录开始:
```console
$ mkdir add
$ cd add
```
接着在 *add* 目录中,创建 *Cargo.toml* 文件。这个 *Cargo.toml* 文件配置了整个工作空间。它不会包含 `[package]` 部分。相反,它以 `[workspace]` 部分作为开始,并通过指定 *adder* 的路径来为工作空间增加成员,如下会加入二进制 crate
接着在 *add* 目录中,创建 *Cargo.toml* 文件,用于配置整个整个工作空间。它不会包含 `[package]` 部分。相反,它以 `[workspace]` 部分作为开始,允许我们向工作区添加成员。我们还通过将 `resolver` 设置为 `"3"`,在工作区中使用 Cargo 最新且最强大的解析算法。
<span class="filename">文件名Cargo.toml</span>
@ -31,6 +30,12 @@ $ cargo new adder
Adding `adder` as member of workspace at `file:///projects/add`
```
在工作空间中运行 `cargo new` 也会自动将新建包加入到工作空间 `Cargo.toml``[workspace]` 定义的 `members` 键中,像这样:
```toml
{{#include ../listings/ch14-more-about-cargo/output-only-01-adder-crate/add/Cargo.toml}}
```
到此为止,可以运行 `cargo build` 来构建工作空间。*add* 目录中的文件应该看起来像这样:
```text
@ -43,11 +48,19 @@ $ cargo new adder
└── target
```
工作空间在顶级目录有一个 *target* 目录;`adder` 并没有自己的 *target* 目录。即使进入 *adder* 目录运行 `cargo build`,构建结果也位于 *add/target* 而不是 *add/adder/target*。工作空间中的 crate 之间相互依赖。如果每个 crate 有其自己的 *target* 目录,为了在自己的 *target* 目录中生成构建结果,工作空间中的每一个 crate 都不得不相互重新编译其他 crate。通过共享一个 *target* 目录,工作空间可以避免其他 crate 重复构建。
工作空间在顶级目录有一个 *target* 目录,用于存放编译生成的产物`adder` 并没有自己的 *target* 目录。即使进入 *adder* 目录运行 `cargo build`,构建结果也位于 *add/target* 而不是 *add/adder/target*。工作空间中的 crate 之间相互依赖。如果每个 crate 有其自己的 *target* 目录,为了在自己的 *target* 目录中生成构建结果,工作空间中的每一个 crate 都不得不相互重新编译其他 crate。通过共享一个 *target* 目录,工作空间可以避免其他 crate 重复构建。
### 在工作空间中创建第二个包
接下来,让我们在工作空间中指定另一个成员 crate。这个 crate 位于 *add_one* 目录中,所以修改顶级 *Cargo.toml* 为也包含 *add_one* 路径:
接下来,让我们在工作空间中创建另一个成员包,并将其命名为 `add_one`。生成一个名为 `add_one` 的库 crate
```console
$ cargo new add_one --lib
Creating library `add_one` package
Adding `add_one` as member of workspace at `file:///projects/add`
```
现在顶层的 *Cargo.toml*`members` 列表将会包含 add_one* 路径:
<span class="filename">文件名Cargo.toml</span>
@ -55,13 +68,6 @@ $ cargo new adder
{{#include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/Cargo.toml}}
```
接着新生成一个叫做 `add_one` 的库:
```console
$ cargo new add_one --lib
Created library `add_one` package
```
现在 *add* 目录应该有如下目录和文件:
```text
@ -86,7 +92,7 @@ $ cargo new add_one --lib
{{#rustdoc_include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/add_one/src/lib.rs}}
```
现在我们有了二进制 `adder` 依赖库 crate `add_one`。首先需要在 *adder/Cargo.toml* 文件中增加 `add_one` 作为路径依赖:
现在我们有了二进制 `adder` 依赖库 crate `add_one`。首先需要在 *adder/Cargo.toml* 文件中增加 `add_one` 作为路径依赖:
<span class="filename">文件名adder/Cargo.toml</span>
@ -94,7 +100,7 @@ $ cargo new add_one --lib
{{#include ../listings/ch14-more-about-cargo/no-listing-02-workspace-with-two-crates/add/adder/Cargo.toml:6:7}}
```
cargo 并不假定工作空间中的 Crates 会相互依赖,所以需要明确表明工作空间中 crate 的依赖关系。
cargo 并不假定工作空间中的 Crates 会相互依赖,所以需要显式表明工作空间中 crate 的依赖关系。
接下来,在 `adder` crate 中使用( `add_one` crate 中的)函数 `add_one`。打开 *adder/src/main.rs* 在顶部增加一行 `use` 将新 `add_one` 库 crate 引入作用域。接着修改 `main` 函数来调用 `add_one` 函数,如示例 14-7 所示。
@ -106,20 +112,20 @@ cargo 并不假定工作空间中的 Crates 会相互依赖,所以需要明确
<span class="caption">示例 14-7`adder` crate 中使用 `add_one` 库 crate</span>
*add* 目录中运行 `cargo build` 来构建工作空间!
顶层 *add* 目录中运行 `cargo build` 来构建工作空间!
```console
$ 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.68s
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.22s
```
为了在顶层 *add* 目录运行二进制 crate可以通过 `-p` 参数和包名称来运行 `cargo run` 指定工作空间中我们希望使用的包:
```console
$ cargo run -p adder
Finished dev [unoptimized + debuginfo] target(s) in 0.0s
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.00s
Running `target/debug/adder`
Hello, world! 10 plus one is 11!
```
@ -137,7 +143,7 @@ Hello, world! 10 plus one is 11!
{{#include ../listings/ch14-more-about-cargo/no-listing-03-workspace-with-external-dependency/add/add_one/Cargo.toml:6:7}}
```
现在就可以在 *add_one/src/lib.rs* 中增加 `use rand;` 了,接着在 *add* 目录运行 `cargo build` 构建整个工作空间就会引入并编译 `rand` crate
现在就可以在 *add_one/src/lib.rs* 中增加 `use rand;` 了,接着在 *add* 目录运行 `cargo build` 构建整个工作空间就会引入并编译 `rand` crate。我们会收到一个警告,因为我们并没有引用已导入作用域的 `rand`
```console
$ cargo build
@ -154,9 +160,9 @@ warning: unused import: `rand`
|
= note: `#[warn(unused_imports)]` on by default
warning: `add_one` (lib) generated 1 warning
warning: `add_one` (lib) generated 1 warning (run `cargo fix --lib -p add_one` to apply 1 suggestion)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished dev [unoptimized + debuginfo] target(s) in 10.18s
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.95s
```
现在顶级的 *Cargo.lock* 包含了 `add_one``rand` 依赖的信息。然而,即使 `rand` 被用于工作空间的某处,也不能在其他 crate 中使用它,除非也在它们的 *Cargo.toml* 中加入 `rand`。例如,如果在顶级的 `adder` crate 的 *adder/src/main.rs* 中增加 `use rand;`,会得到一个错误:
@ -174,9 +180,11 @@ error[E0432]: unresolved import `rand`
为了修复这个错误,修改顶级 `adder` crate 的 *Cargo.toml* 来表明 `rand` 也是这个 crate 的依赖。构建 `adder` crate 会将 `rand` 加入到 *Cargo.lock*`adder` 的依赖列表中,但是这并不会下载 `rand` 的额外拷贝。Cargo 确保了工作空间中任何使用 `rand` 的 crate 都采用相同的版本,这节省了空间并确保了工作空间中的 crate 将是相互兼容的。
如果工作空间中的 crate 指定了不兼容的同一依赖的不同版本Cargo 会解析它们,但仍会尽量减少解析的版本数量。
#### 为工作空间增加测试
作为另一个提升,让我们为 `add_one` crate 中的 `add_one::add_one` 函数增加一个测试:
作为另一个改进,让我们为 `add_one` crate 中的 `add_one::add_one` 函数增加一个测试:
<span class="filename">文件名add_one/src/lib.rs</span>
@ -190,15 +198,15 @@ error[E0432]: unresolved import `rand`
$ cargo test
Compiling add_one v0.1.0 (file:///projects/add/add_one)
Compiling adder v0.1.0 (file:///projects/add/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.27s
Running unittests src/lib.rs (target/debug/deps/add_one-f0253159197f7841)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.20s
Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Running unittests src/main.rs (target/debug/deps/adder-49979ff40686fa8e)
Running unittests src/main.rs (target/debug/deps/adder-3a47283c568d2b6a)
running 0 tests
@ -211,14 +219,14 @@ running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
```
输出的第一部分显示 `add_one` crate 的 `it_works` 测试通过了。下一个部分显示 `adder` crate 中找到了 0 个测试,最后一部分显示 `add_one` crate 中有 0 个文档测试。
输出的第一部分显示 `add_one` crate 的 `it_works` 测试通过了。下一个部分显示 `adder` crate 中找到了零个测试,最后一部分显示 `add_one` crate 中有零个文档测试。
也可以选择运行工作空间中特定 crate 的测试,通过在根目录使用 `-p` 参数并指定希望测试的 crate 名称:
```console
$ cargo test -p add_one
Finished test [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/lib.rs (target/debug/deps/add_one-b3235fea9a156f74)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.00s
Running unittests src/lib.rs (target/debug/deps/add_one-93c49ee75dc46543)
running 1 test
test tests::it_works ... ok
@ -234,7 +242,7 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; fini
输出显示了 `cargo test` 只运行了 `add_one` crate 的测试而没有运行 `adder` crate 的测试。
如果你选择向 [crates.io](https://crates.io/)发布工作空间中的 crate每一个工作空间中的 crate 需要单独发布。就像 `cargo test` 一样,可以通过 `-p` 参数并指定期望发布的 crate 名来发布工作空间中的某个特定的 crate。
如果你选择向 [crates.io](https://crates.io/) 发布工作空间中的 crate每一个工作空间中的 crate 需要单独发布。就像 `cargo test` 一样,可以通过 `-p` 参数并指定期望发布的 crate 名来发布工作空间中的某个特定的 crate。
现在尝试以类似 `add_one` crate 的方式向工作空间增加 `add_two` crate 来作为更多的练习!

Loading…
Cancel
Save