diff --git a/proofreading_prompt.md b/proofreading_prompt.md index 8616b80..29a321d 100644 --- a/proofreading_prompt.md +++ b/proofreading_prompt.md @@ -14,13 +14,34 @@ - 英文原文: ``` +Notice that we don’t include the `unsafe` keyword in this code. We can create +raw pointers in safe code; we just can’t dereference raw pointers outside an +unsafe block, as you’ll see in a bit. +We’ve created raw pointers by using the raw borrow operators: `&raw const num` +creates a `*const i32` immutable raw pointer, and `&raw mut num` creates a `*mut +i32` mutable raw pointer. Because we created them directly from a local +variable, we know these particular raw pointers are valid, but we can’t make +that assumption about just any raw pointer. + +To demonstrate this, next we’ll create a raw pointer whose validity we can’t be +so certain of, using `as` to cast a value instead of using the raw borrow +operators. Listing 20-2 shows how to create a raw pointer to an arbitrary +location in memory. Trying to use arbitrary memory is undefined: there might be +data at that address or there might not, the compiler might optimize the code so +there is no memory access, or the program might terminate with a segmentation +fault. Usually, there is no good reason to write code like this, especially in +cases where you can use a raw borrow operator instead, but it is possible. ``` - 中文翻译: ``` +注意这里没有引入 `unsafe` 关键字。可以在安全代码中 **创建** 裸指针,只是不能在不安全块之外 **解引用** 裸指针,稍后便会看到。 +这里使用 `as` 将不可变和可变引用强转为对应的裸指针类型。因为直接从保证安全的引用来创建它们,可以知道这些特定的裸指针是有效,但是不能对任何裸指针做出如此假设。 + +作为展示接下来会创建一个不能确定其有效性的裸指针,示例 19-2 展示了如何创建一个指向任意内存地址的裸指针。尝试使用任意内存是未定义行为:此地址可能有数据也可能没有,编译器可能会优化掉这个内存访问,或者程序可能会出现段错误(segmentation fault)。通常没有好的理由编写这样的代码,不过却是可行的: ``` **输出:** diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 071e870..c4e4026 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -123,10 +123,10 @@ - [高级特性](ch20-00-advanced-features.md) - [不安全的 Rust](ch20-01-unsafe-rust.md) - - [高级 trait](ch20-03-advanced-traits.md) - - [高级类型](ch20-04-advanced-types.md) - - [高级函数与闭包](ch20-05-advanced-functions-and-closures.md) - - [宏](ch20-06-macros.md) + - [高级 trait](ch20-02-advanced-traits.md) + - [高级类型](ch20-03-advanced-types.md) + - [高级函数与闭包](ch20-04-advanced-functions-and-closures.md) + - [宏](ch20-05-macros.md) - [最后的项目:构建多线程 web server](ch21-00-final-project-a-web-server.md) - [建立单线程 web server](ch21-01-single-threaded.md) diff --git a/src/ch20-00-advanced-features.md b/src/ch20-00-advanced-features.md index 978fd97..742b08e 100644 --- a/src/ch20-00-advanced-features.md +++ b/src/ch20-00-advanced-features.md @@ -2,16 +2,16 @@ > [ch20-00-advanced-features.md](https://github.com/rust-lang/book/blob/main/src/ch20-00-advanced-features.md) >
-> commit a8536189d5f1ba2f0b1187bfae787c0d89e2871c +> commit 3a30e4c1fbe641afc066b3af9eb01dcdf5ed8b24 -现在我们已经学习了 Rust 编程语言中最常用的部分。在第二十一章开始另一个新项目之前,让我们聊聊一些总有一天你会遇上的部分内容。你可以将本章作为不经意间遇到未知的内容时的参考。本章将要学习的功能在一些非常特定的场景下很有用处。虽然很少会碰到它们,我们希望确保你了解 Rust 提供的所有功能。 +现在我们已经学习了 Rust 编程语言中最常用的部分。在第二十一章开始另一个新项目之前,我们先来了解一些你偶尔可能会遇到,但并不会每天都用的语言特性。你可以将本章作为不经意间遇到未知的内容时的参考。本章将要学习的功能在一些非常特定的场景下非常有用。虽然很少会碰到它们,我们希望确保你了解 Rust 提供的所有功能。 本章将涉及如下内容: * 不安全 Rust:用于当需要舍弃 Rust 的某些保证并负责手动维持这些保证 -* 高级 trait:与 trait 相关的关联类型,默认类型参数,完全限定语法(fully qualified syntax),超(父)trait(supertraits)和 newtype 模式 +* 高级 trait:与 trait 相关的关联类型,默认类型参数,完全限定语法(fully qualified syntax),超(父)trait(supertraits)模式 newtype 模式 * 高级类型:关于 newtype 模式的更多内容,类型别名,never 类型和动态大小类型 * 高级函数和闭包:函数指针和返回闭包 * 宏:定义在编译时定义更多代码的方式 -对所有人而言,这都是一个介绍 Rust 迷人特性的宝典!让我们翻开它吧! +对所有人而言,这都是一个介绍 Rust 迷人特性的宝典!让我们开始探索吧! diff --git a/src/ch20-01-unsafe-rust.md b/src/ch20-01-unsafe-rust.md index 4530a99..60ca78e 100644 --- a/src/ch20-01-unsafe-rust.md +++ b/src/ch20-01-unsafe-rust.md @@ -2,19 +2,17 @@ > [ch20-01-unsafe-rust.md](https://github.com/rust-lang/book/blob/main/src/ch20-01-unsafe-rust.md) >
-> commit 057b0d338229b87d06be772346ea0b643bdecdc5 +> commit 4b2590f90c1d72002278a8a426a94a9eadd4d498 目前为止讨论过的代码都有 Rust 在编译时会强制执行的内存安全保证。然而,Rust 还隐藏有第二种语言,它不会强制执行这类内存安全保证:这被称为 **不安全 Rust**(*unsafe Rust*)。它与常规 Rust 代码无异,但是会提供额外的超能力。 -尽管代码可能没问题,但如果 Rust 编译器没有足够的信息可以确定,它将拒绝代码。 +不安全 Rust 之所以存在,是因为静态分析本质上是保守的。当编译器尝试确定一段代码是否支持某个保证时,拒绝一些合法的程序比接受无效的程序要好一些。这必然意味着有时代码**可能**是合法的,但如果 Rust 编译器没有足够的信息来确定,它将拒绝该代码。在这种情况下,可以使用不安全代码告诉编译器,“相信我,我知道自己在干什么。” 不过千万注意,使用不安全 Rust 风险自担:如果不安全代码出错了,比如解引用空指针,可能会导致不安全的内存使用。 -不安全 Rust 之所以存在,是因为静态分析本质上是保守的。当编译器尝试确定一段代码是否支持某个保证时,拒绝一些合法的程序比接受无效的程序要好一些。这必然意味着有时代码 **可能** 是合法的,但如果 Rust 编译器没有足够的信息来确定,它将拒绝该代码。在这种情况下,可以使用不安全代码告诉编译器,“相信我,我知道我在干什么。” 不过千万注意,使用不安全 Rust 风险自担:如果不安全代码出错了,比如解引用空指针,可能会导致不安全的内存使用。 - -另一个 Rust 存在不安全一面的原因是:底层计算机硬件固有的不安全性。如果 Rust 不允许进行不安全操作,那么有些任务则根本完成不了。Rust 需要能够进行像直接与操作系统交互,甚至于编写你自己的操作系统这样的底层系统编程!这也是 Rust 语言的目标之一。让我们看看不安全 Rust 能做什么,和怎么做。 +另一个 Rust 存在不安全一面的原因是底层计算机硬件固有的不安全性。如果 Rust 不允许进行不安全操作,那么有些任务则根本完成不了。Rust 需要能够进行像直接与操作系统交互甚至于编写你自己的操作系统这样的底层系统编程。底层系统编程也是 Rust 语言的目标之一。让我们看看不安全 Rust 能做什么,和怎么做。 ### 不安全的超能力 -可以通过 `unsafe` 关键字来切换到不安全 Rust,接着可以开启一个新的存放不安全代码的块。这里有五类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作,它们称之为 “不安全的超能力。(*unsafe superpowers*)” 这些超能力是: +要切换到 `unsafe Rust`,可以使用 `unsafe` 关键字,然后开启一个包含不安全代码的新块。这里有五类可以在不安全 Rust 中进行而不能用于安全 Rust 的操作,它们称之为**不安全的超能力**(**unsafe superpowers**)。这些超能力包括: * 解引用裸指针 * 调用不安全的函数或方法 @@ -24,17 +22,17 @@ 有一点很重要,`unsafe` 并不会关闭借用检查器或禁用任何其他 Rust 安全检查:如果在不安全代码中使用引用,它仍会被检查。`unsafe` 关键字只是提供了那五个不会被编译器检查内存安全的功能。你仍然能在不安全块中获得某种程度的安全。 -再者,`unsafe` 不意味着块中的代码就一定是危险的或者必然导致内存安全问题:其意图在于作为程序员你将会确保 `unsafe` 块中的代码以有效的方式访问内存。 +再者,`unsafe` 不意味着块中的代码就一定是危险的或者必然导致内存安全问题:其意图在于作为程序员,你将会确保 `unsafe` 块中的代码以有效的方式访问内存。 -人是会犯错误的,错误总会发生,不过通过要求这五类操作必须位于标记为 `unsafe` 的块中,就能够知道任何与内存安全相关的错误必定位于 `unsafe` 块内。保持 `unsafe` 块尽可能小,如此当之后调查内存 bug 时就会感谢你自己了。 +人难免出错,错误总会发生,不过通过要求这五类不安全操作必须位于标记为 `unsafe` 的块中,就能够知道任何与内存安全相关的错误必定位于 `unsafe` 块内。保持 `unsafe` 块尽可能小;如此当之后调查内存 bug 时就会感谢你自己了。 -为了尽可能隔离不安全代码,将不安全代码封装进一个安全的抽象并提供安全 API 是一个好主意,当我们学习不安全函数和方法时会讨论到。标准库的一部分被实现为在被评审过的不安全代码之上的安全抽象。这个技术防止了 `unsafe` 泄露到所有你或者用户希望使用由 `unsafe` 代码实现的功能的地方,因为使用其安全抽象是安全的。 +为了尽可能隔离不安全代码,最好将不安全代码封装进一个安全的抽象并提供安全 API,当我们学习不安全函数和方法时会讨论到。标准库的一部分被实现为在被评审过的不安全代码之上的安全抽象。这个技术防止了 `unsafe` 泄露到所有你或者用户希望使用由 `unsafe` 代码实现的功能的地方,因为使用其安全抽象是安全的。 -让我们按顺序依次介绍上述五个超能力,同时我们会看到一些提供不安全代码的安全接口的抽象。 +让我们按顺序依次介绍上述五类超能力,同时我们会看到一些提供不安全代码的安全接口的抽象。 ### 解引用裸指针 -回到第四章的 [“悬垂引用”][dangling-references] 部分,那里提到了编译器会确保引用总是有效的。不安全 Rust 有两个被称为 **裸指针**(*raw pointers*)的类似于引用的新类型。和引用一样,裸指针是不可变或可变的,分别写作 `*const T` 和 `*mut T`。这里的星号不是解引用运算符;它是类型名称的一部分。在裸指针的上下文中,**不可变** 意味着指针解引用之后不能直接赋值。 +回到第四章的[“悬垂引用”][dangling-references]一节,那里提到了编译器会确保引用总是有效的。不安全 Rust 有两个被称为 **裸指针**(*raw pointers*)的类似于引用的新类型。和引用一样,裸指针是不可变或可变的,分别写作 `*const T` 和 `*mut T`。这里的星号不是解引用运算符;它是类型名称的一部分。在裸指针的上下文中,**不可变** 意味着指针解引用之后不能直接赋值。 裸指针与引用和智能指针的区别在于 @@ -45,37 +43,37 @@ 通过去掉 Rust 强加的保证,你可以放弃安全保证以换取性能或使用另一个语言或硬件接口的能力,此时 Rust 的保证并不适用。 -示例 19-1 展示了如何从引用同时创建不可变和可变裸指针。 +示例 20-1 展示了如何创建一个不可变裸指针和一个可变裸指针。 ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-01/src/main.rs:here}} ``` -示例 19-1: 通过引用创建裸指针 +示例 20-1: 通过引用创建裸指针 -注意这里没有引入 `unsafe` 关键字。可以在安全代码中 **创建** 裸指针,只是不能在不安全块之外 **解引用** 裸指针,稍后便会看到。 +注意这段代码中没有引入 `unsafe` 关键字。可以在安全代码中创建裸指针;只是不能在不安全块之外解引用裸指针,稍后便会看到。 -这里使用 `as` 将不可变和可变引用强转为对应的裸指针类型。因为直接从保证安全的引用来创建它们,可以知道这些特定的裸指针是有效,但是不能对任何裸指针做出如此假设。 +我们通过使用裸指针借用操作符(raw borrow operators)创建裸指针:`&raw const num` 会创建一个 `*const i32` 的不可变裸指针。因为由于我们是直接从一个局部变量创建它们的,因此可以确定这些特定的裸指针是有效的,但是不能对任何裸指针都做出如此假设。 -作为展示接下来会创建一个不能确定其有效性的裸指针,示例 19-2 展示了如何创建一个指向任意内存地址的裸指针。尝试使用任意内存是未定义行为:此地址可能有数据也可能没有,编译器可能会优化掉这个内存访问,或者程序可能会出现段错误(segmentation fault)。通常没有好的理由编写这样的代码,不过却是可行的: +为了演示这一点,接下来我们将创建一个有效性无法确定的裸指针,使用 `as` 进行类型转换而不是使用裸指针借用操作符。示例 20-2 展示了如何创建一个指向任意内存地址的裸指针。尝试使用任意内存是未定义行为:此地址可能有数据也可能没有,编译器可能会优化掉这个内存访问,或者程序可能因段错误(segmentation fault)而终止。通常在有裸指针借用操作符可用的情况下,没有充分的理由编写这样的代码,但这确实是可行的。 ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-02/src/main.rs:here}} ``` -示例 19-2: 创建指向任意内存地址的裸指针 +示例 20-2: 创建指向任意内存地址的裸指针 -记得我们说过可以在安全代码中创建裸指针,不过不能 **解引用** 裸指针和读取其指向的数据。现在我们要做的就是对裸指针使用解引用运算符 `*`,这需要一个 `unsafe` 块,如示例 19-3 所示: +记得我们说过可以在安全代码中创建裸指针,不过不能 **解引用** 裸指针和读取其指向的数据。现在我们要做的就是对裸指针使用解引用运算符 `*`,这需要一个 `unsafe` 块,如示例 20-3 所示: ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-03/src/main.rs:here}} ``` -示例 19-3: 在 `unsafe` 块中解引用裸指针 +示例 20-3: 在 `unsafe` 块中解引用裸指针 创建一个指针不会造成任何危险;只有当访问其指向的值时才有可能遇到无效的值。 -还需注意示例 19-1 和 19-3 中创建了同时指向相同内存位置 `num` 的裸指针 `*const i32` 和 `*mut i32`。相反如果尝试同时创建 `num` 的不可变和可变引用,将无法通过编译,因为 Rust 的所有权规则不允许在拥有任何不可变引用的同时再创建一个可变引用。通过裸指针,就能够同时创建同一地址的可变指针和不可变指针,若通过可变指针修改数据,则可能潜在造成数据竞争。请多加小心! +还需注意示例 20-1 和 20-3 中创建了同时指向相同内存位置 `num` 的裸指针 `*const i32` 和 `*mut i32`。相反如果尝试同时创建 `num` 的不可变和可变引用,将无法通过编译,因为 Rust 的所有权规则不允许在拥有任何不可变引用的同时再创建一个可变引用。通过裸指针,就能够同时创建同一地址的可变指针和不可变指针,若通过可变指针修改数据,则可能潜在造成数据竞争。请多加小心! 既然存在这么多的危险,为何还要使用裸指针呢?一个主要的应用场景便是调用 C 代码接口,这在下一部分 [“调用不安全函数或方法”](#调用不安全函数或方法) 中会讲到。另一个场景是构建借用检查器无法理解的安全抽象。让我们先介绍不安全函数,接着看一看使用不安全代码的安全抽象的例子。 @@ -101,27 +99,27 @@ #### 创建不安全代码的安全抽象 -仅仅因为函数包含不安全代码并不意味着整个函数都需要标记为不安全的。事实上,将不安全代码封装进安全函数是一个常见的抽象。作为一个例子,了解一下标准库中的函数 `split_at_mut`,它需要一些不安全代码,让我们探索如何可以实现它。这个安全函数定义于可变 slice 之上:它获取一个 slice 并从给定的索引参数开始将其分为两个 slice。`split_at_mut` 的用法如示例 19-4 所示: +仅仅因为函数包含不安全代码并不意味着整个函数都需要标记为不安全的。事实上,将不安全代码封装进安全函数是一个常见的抽象。作为一个例子,了解一下标准库中的函数 `split_at_mut`,它需要一些不安全代码,让我们探索如何可以实现它。这个安全函数定义于可变 slice 之上:它获取一个 slice 并从给定的索引参数开始将其分为两个 slice。`split_at_mut` 的用法如示例 20-4 所示: ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-04/src/main.rs:here}} ``` -示例 19-4: 使用安全的 `split_at_mut` 函数 +示例 20-4: 使用安全的 `split_at_mut` 函数 -这个函数无法只通过安全 Rust 实现。一个尝试可能看起来像示例 19-5,它不能编译。出于简单考虑,我们将 `split_at_mut` 实现为函数而不是方法,并只处理 `i32` 值而非泛型 `T` 的 slice。 +这个函数无法只通过安全 Rust 实现。一个尝试可能看起来像示例 20-5,它不能编译。出于简单考虑,我们将 `split_at_mut` 实现为函数而不是方法,并只处理 `i32` 值而非泛型 `T` 的 slice。 ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-05/src/main.rs:here}} ``` -示例 19-5: 尝试只使用安全 Rust 来实现 `split_at_mut` +示例 20-5: 尝试只使用安全 Rust 来实现 `split_at_mut` 此函数首先获取 slice 的长度,然后通过检查参数是否小于或等于这个长度来断言参数所给定的索引位于 slice 当中。该断言意味着如果传入的索引比要分割的 slice 的索引更大,此函数在尝试使用这个索引前 panic。 之后我们在一个元组中返回两个可变的 slice:一个从原始 slice 的开头直到 `mid` 索引,另一个从 `mid` 直到原 slice 的结尾。 -如果尝试编译示例 19-5 的代码,会得到一个错误: +如果尝试编译示例 20-5 的代码,会得到一个错误: ```console {{#include ../listings/ch20-advanced-features/listing-20-05/output.txt}} @@ -129,13 +127,13 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两次。本质上借用 slice 的不同部分是可以的,因为结果两个 slice 不会重叠,不过 Rust 还没有智能到能够理解这些。当我们知道某些事是可以的而 Rust 不知道的时候,就是触及不安全代码的时候了 -示例 19-6 展示了如何使用 `unsafe` 块,裸指针和一些不安全函数调用来实现 `split_at_mut`: +示例 20-6 展示了如何使用 `unsafe` 块,裸指针和一些不安全函数调用来实现 `split_at_mut`: ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-06/src/main.rs:here}} ``` -示例 19-6: 在 `split_at_mut` 函数的实现中使用不安全代码 +示例 20-6: 在 `split_at_mut` 函数的实现中使用不安全代码 回忆第四章的 [“Slice 类型” ][the-slice-type] 部分,slice 是一个指向一些数据的指针,并带有该 slice 的长度。可以使用 `len` 方法获取 slice 的长度,使用 `as_mut_ptr` 方法访问 slice 的裸指针。在这个例子中,因为有一个 `i32` 值的可变 slice,`as_mut_ptr` 返回一个 `*mut i32` 类型的裸指针,储存在 `ptr` 变量中。 @@ -145,13 +143,13 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同 注意无需将 `split_at_mut` 函数的结果标记为 `unsafe`,并可以在安全 Rust 中调用此函数。我们创建了一个不安全代码的安全抽象,其代码以一种安全的方式使用了 `unsafe` 代码,因为其只从这个函数访问的数据中创建了有效的指针。 -与此相对,示例 19-7 中的 `slice::from_raw_parts_mut` 在使用 slice 时很有可能会崩溃。这段代码获取任意内存地址并创建了一个长为一万的 slice: +与此相对,示例 20-7 中的 `slice::from_raw_parts_mut` 在使用 slice 时很有可能会崩溃。这段代码获取任意内存地址并创建了一个长为一万的 slice: ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-07/src/main.rs:here}} ``` -示例 19-7: 通过任意内存地址创建 slice +示例 20-7: 通过任意内存地址创建 slice 我们并不拥有这个任意地址的内存,也不能保证这段代码创建的 slice 包含有效的 `i32` 值。试图使用臆测为有效的 `values` 会导致未定义的行为。 @@ -159,7 +157,7 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同 有时你的 Rust 代码可能需要与其他语言编写的代码交互。为此 Rust 有一个关键字,`extern`,有助于创建和使用 **外部函数接口**(*Foreign Function Interface*,FFI)。外部函数接口是一个编程语言用以定义函数的方式,其允许不同(外部)编程语言调用这些函数。 -示例 19-8 展示了如何集成 C 标准库中的 `abs` 函数。`extern` 块中声明的函数在 Rust 代码中总是不安全的。因为其他语言不会强制执行 Rust 的规则且 Rust 无法检查它们,所以确保其安全是程序员的责任: +示例 20-8 展示了如何集成 C 标准库中的 `abs` 函数。`extern` 块中声明的函数在 Rust 代码中总是不安全的。因为其他语言不会强制执行 Rust 的规则且 Rust 无法检查它们,所以确保其安全是程序员的责任: 文件名:src/main.rs @@ -167,7 +165,7 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-08/src/main.rs}} ``` -示例 19-8: 声明并调用另一个语言中定义的 `extern` 函数 +示例 20-8: 声明并调用另一个语言中定义的 `extern` 函数 在 `extern "C"` 块中,列出了我们希望能够调用的另一个语言中的外部函数的签名和名称。`"C"` 部分定义了外部函数所使用的 **应用二进制接口**(*application binary interface*,ABI) —— ABI 定义了如何在汇编语言层面调用此函数。`"C"` ABI 是最常见的,并遵循 C 编程语言的 ABI。 @@ -190,7 +188,7 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同 目前为止全书都尽量避免讨论 **全局变量**(*global variables*),Rust 确实支持它们,不过这对于 Rust 的所有权规则来说是有问题的。如果有两个线程访问相同的可变全局变量,则可能会造成数据竞争。 -全局变量在 Rust 中被称为 **静态**(*static*)变量。示例 19-9 展示了一个拥有字符串 slice 值的静态变量的声明和应用: +全局变量在 Rust 中被称为 **静态**(*static*)变量。示例 20-9 展示了一个拥有字符串 slice 值的静态变量的声明和应用: 文件名:src/main.rs @@ -198,11 +196,11 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-09/src/main.rs}} ``` -示例 19-9: 定义和使用一个不可变静态变量 +示例 20-9: 定义和使用一个不可变静态变量 静态(`static`)变量类似于第三章 [“变量和常量的区别”][differences-between-variables-and-constants] 部分讨论的常量。通常静态变量的名称采用 `SCREAMING_SNAKE_CASE` 写法。静态变量只能储存拥有 `'static` 生命周期的引用,这意味着 Rust 编译器可以自己计算出其生命周期而无需显式标注。访问不可变静态变量是安全的。 -常量与不可变静态变量的一个微妙的区别是静态变量中的值有一个固定的内存地址。使用这个值总是会访问相同的地址。另一方面,常量则允许在任何被用到的时候复制其数据。另一个区别在于静态变量可以是可变的。访问和修改可变静态变量都是 **不安全** 的。示例 19-10 展示了如何声明、访问和修改名为 `COUNTER` 的可变静态变量: +常量与不可变静态变量的一个微妙的区别是静态变量中的值有一个固定的内存地址。使用这个值总是会访问相同的地址。另一方面,常量则允许在任何被用到的时候复制其数据。另一个区别在于静态变量可以是可变的。访问和修改可变静态变量都是 **不安全** 的。示例 20-10 展示了如何声明、访问和修改名为 `COUNTER` 的可变静态变量: 文件名:src/main.rs @@ -210,7 +208,7 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-10/src/main.rs}} ``` -示例 19-10: 读取或修改一个可变静态变量是不安全的 +示例 20-10: 读取或修改一个可变静态变量是不安全的 就像常规变量一样,我们使用 `mut` 关键来指定可变性。任何读写 `COUNTER` 的代码都必须位于 `unsafe` 块中。这段代码可以编译并如期打印出 `COUNTER: 3`,因为这是单线程的。拥有多个线程访问 `COUNTER` 则可能导致数据竞争。 @@ -218,13 +216,13 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同 ### 实现不安全 trait -`unsafe` 的另一个操作用例是实现不安全 trait。当 trait 中至少有一个方法中包含编译器无法验证的不变式(invariant)时 trait 是不安全的。可以在 `trait` 之前增加 `unsafe` 关键字将 trait 声明为 `unsafe`,同时 trait 的实现也必须标记为 `unsafe`,如示例 19-11 所示: +`unsafe` 的另一个操作用例是实现不安全 trait。当 trait 中至少有一个方法中包含编译器无法验证的不变式(invariant)时 trait 是不安全的。可以在 `trait` 之前增加 `unsafe` 关键字将 trait 声明为 `unsafe`,同时 trait 的实现也必须标记为 `unsafe`,如示例 20-11 所示: ```rust {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-11/src/main.rs}} ``` -示例 19-11: 定义并实现不安全 trait +示例 20-11: 定义并实现不安全 trait 通过 `unsafe impl`,我们承诺将保证编译器所不能验证的不变量。 diff --git a/src/ch20-03-advanced-traits.md b/src/ch20-02-advanced-traits.md similarity index 90% rename from src/ch20-03-advanced-traits.md rename to src/ch20-02-advanced-traits.md index b7ecbf3..64b717d 100644 --- a/src/ch20-03-advanced-traits.md +++ b/src/ch20-02-advanced-traits.md @@ -1,6 +1,6 @@ ## 高级 trait -> [ch20-03-advanced-traits.md](https://github.com/rust-lang/book/blob/main/src/ch20-03-advanced-traits.md) +> [ch20-02-advanced-traits.md](https://github.com/rust-lang/book/blob/main/src/ch20-02-advanced-traits.md) >
> commit 95e931170404cb98d476b19017cbbdbc00d0834d @@ -12,13 +12,13 @@ 我们之前提到,本章所描述的大部分内容都较少使用。关联类型则比较适中;它们比本书其他的内容要少见,不过比本章中的很多内容要更常见。 -一个带有关联类型的 trait 的例子是标准库提供的 `Iterator` trait。它有一个叫做 `Item` 的关联类型来替代遍历的值的类型。`Iterator` trait 的定义如示例 19-12 所示: +一个带有关联类型的 trait 的例子是标准库提供的 `Iterator` trait。它有一个叫做 `Item` 的关联类型来替代遍历的值的类型。`Iterator` trait 的定义如示例 20-13 所示: ```rust,noplayground -{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-12/src/lib.rs}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-13/src/lib.rs}} ``` -示例 19-12: `Iterator` trait 的定义中带有关联类型 `Item` +示例 20-13: `Iterator` trait 的定义中带有关联类型 `Item` `Item` 是一个占位符类型,同时 `next` 方法的定义表明它返回 `Option` 类型的值。这个 trait 的实现者会指定 `Item` 的具体类型,无论实现者指定何种类型,`next` 方法都会返回一个包含了此具体类型值的 `Option`。 @@ -31,17 +31,17 @@ {{#rustdoc_include ../listings/ch20-advanced-features/no-listing-22-iterator-on-counter/src/lib.rs:ch19}} ``` -这个语法类似于泛型。那么为什么 `Iterator` trait 不像下面示例 19-13 那样,使用泛型来定义呢? +这个语法类似于泛型。那么为什么 `Iterator` trait 不像下面示例 20-13 那样,使用泛型来定义呢? ```rust,noplayground {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-13/src/lib.rs}} ``` -示例 19-13: 一个使用泛型的 `Iterator` trait 假想定义 +示例 20-13: 一个使用泛型的 `Iterator` trait 假想定义 -区别在于当如示例 19-13 那样使用泛型时,则不得不在每一个实现中标注类型。这是因为我们也可以实现为 `Iterator for Counter`,或任何其他类型,这样就可以有多个 `Counter` 的 `Iterator` 的实现。换句话说,当 trait 有泛型参数时,可以多次实现这个 trait,每次需改变泛型参数的具体类型。接着当使用 `Counter` 的 `next` 方法时,必须提供类型注解来表明希望使用 `Iterator` 的哪一个实现。 +区别在于当如示例 20-13 那样使用泛型时,则不得不在每一个实现中标注类型。这是因为我们也可以实现为 `Iterator for Counter`,或任何其他类型,这样就可以有多个 `Counter` 的 `Iterator` 的实现。换句话说,当 trait 有泛型参数时,可以多次实现这个 trait,每次需改变泛型参数的具体类型。接着当使用 `Counter` 的 `next` 方法时,必须提供类型注解来表明希望使用 `Iterator` 的哪一个实现。 -有了关联类型,在实现时就无需标注类型,因为不能多次实现这个 trait。对于示例 19-12 使用关联类型的定义,我们只能选择一次 `Item` 会是什么类型,因为只能有一个 `impl Iterator for Counter`。当调用 `Counter` 的 `next` 时不必每次指定我们需要 `u32` 值的迭代器。 +有了关联类型,在实现时就无需标注类型,因为不能多次实现这个 trait。对于示例 20-12 使用关联类型的定义,我们只能选择一次 `Item` 会是什么类型,因为只能有一个 `impl Iterator for Counter`。当调用 `Counter` 的 `next` 时不必每次指定我们需要 `u32` 值的迭代器。 关联类型也会成为 trait 契约的一部分:trait 的实现必须提供一个类型来替代关联类型占位符。关联类型通常以它的用途来命名,并且我们最好在 API 文档中为关联类型编写文档。 @@ -51,15 +51,15 @@ 这种情况的一个非常好的例子是使用 **运算符重载**(*Operator overloading*),这是指在特定情况下自定义运算符(比如 `+`)行为的操作。 -Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。例如,示例 19-14 中展示了如何在 `Point` 结构体上实现 `Add` trait 来重载 `+` 运算符,这样就可以将两个 `Point` 实例相加了: +Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std::ops` 中所列出的运算符和相应的 trait 可以通过实现运算符相关 trait 来重载。例如,示例 20-14 中展示了如何在 `Point` 结构体上实现 `Add` trait 来重载 `+` 运算符,这样就可以将两个 `Point` 实例相加了: 文件名:src/main.rs ```rust -{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-14/src/main.rs}} +{{#rustdoc_include ../listings/ch20-advanced-features/listing-20-15/src/main.rs}} ``` -示例 19-14: 实现 `Add` trait 重载 `Point` 实例的 `+` 运算符 +示例 20-14: 实现 `Add` trait 重载 `Point` 实例的 `+` 运算符 `add` 方法将两个 `Point` 实例的 `x` 值和 `y` 值分别相加来创建一个新的 `Point`。`Add` trait 有一个叫做 `Output` 的关联类型,它用来决定 `add` 方法的返回值类型。 @@ -77,7 +77,7 @@ trait Add { 当为 `Point` 实现 `Add` 时,使用了默认的 `Rhs`,因为我们希望将两个 `Point` 实例相加。让我们看看一个实现 `Add` trait 时希望自定义 `Rhs` 类型而不是使用默认类型的例子。 -这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters`。(这种将现有类型简单封装进另一个结构体的方式被称为 **newtype 模式**(*newtype pattern*,之后的 [“为了类型安全和抽象而使用 newtype 模式”][newtype] 部分会详细介绍。)我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `Rhs`,如示例 19-15 所示。 +这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters`。(这种将现有类型简单封装进另一个结构体的方式被称为 **newtype 模式**(*newtype pattern*,之后的 [“为了类型安全和抽象而使用 newtype 模式”][newtype] 部分会详细介绍。)我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `Rhs`,如示例 20-15 所示。 文件名:src/lib.rs @@ -85,7 +85,7 @@ trait Add { {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-15/src/lib.rs}} ``` -示例 19-15: 在 `Millimeters` 上实现 `Add`,以便能够将 `Millimeters` 与 `Meters` 相加 +示例 20-15: 在 `Millimeters` 上实现 `Add`,以便能够将 `Millimeters` 与 `Meters` 相加 为了使 `Millimeters` 和 `Meters` 能够相加,我们指定 `impl Add` 来设定 `Rhs` 类型参数的值而不是使用默认的 `Self`。 @@ -102,7 +102,7 @@ trait Add { Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法,也不能阻止为同一类型同时实现这两个 trait。甚至直接在类型上实现开始已经有的同名方法也是可能的! -不过,当调用这些同名方法时,需要告诉 Rust 我们希望使用哪一个。考虑一下示例 19-16 中的代码,这里定义了 trait `Pilot` 和 `Wizard` 都拥有方法 `fly`。接着在一个本身已经实现了名为 `fly` 方法的类型 `Human` 上实现这两个 trait。每一个 `fly` 方法都进行了不同的操作: +不过,当调用这些同名方法时,需要告诉 Rust 我们希望使用哪一个。考虑一下示例 20-16 中的代码,这里定义了 trait `Pilot` 和 `Wizard` 都拥有方法 `fly`。接着在一个本身已经实现了名为 `fly` 方法的类型 `Human` 上实现这两个 trait。每一个 `fly` 方法都进行了不同的操作: 文件名:src/main.rs @@ -110,9 +110,9 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-16/src/main.rs:here}} ``` -示例 19-16: 两个 trait 定义为拥有 `fly` 方法,并在直接定义有 `fly` 方法的 `Human` 类型上实现这两个 trait +示例 20-16: 两个 trait 定义为拥有 `fly` 方法,并在直接定义有 `fly` 方法的 `Human` 类型上实现这两个 trait -当调用 `Human` 实例的 `fly` 时,编译器默认调用直接实现在类型上的方法,如示例 19-17 所示。 +当调用 `Human` 实例的 `fly` 时,编译器默认调用直接实现在类型上的方法,如示例 20-17 所示。 文件名:src/main.rs @@ -120,11 +120,11 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-17/src/main.rs:here}} ``` -示例 19-17: 调用 `Human` 实例的 `fly` +示例 20-17: 调用 `Human` 实例的 `fly` 运行这段代码会打印出 `*waving arms furiously*`,这表明 Rust 调用了直接实现在 `Human` 上的 `fly` 方法。 -为了能够调用 `Pilot` trait 或 `Wizard` trait 的 `fly` 方法,我们需要使用更明显的语法以便能指定我们指的是哪个 `fly` 方法。这个语法展示在示例 19-18 中: +为了能够调用 `Pilot` trait 或 `Wizard` trait 的 `fly` 方法,我们需要使用更明显的语法以便能指定我们指的是哪个 `fly` 方法。这个语法展示在示例 20-18 中: 文件名:src/main.rs @@ -132,9 +132,9 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-18/src/main.rs:here}} ``` -示例 19-18: 指定我们希望调用哪一个 trait 的 `fly` 方法 +示例 20-18: 指定我们希望调用哪一个 trait 的 `fly` 方法 -在方法名前指定 trait 名向 Rust 澄清了我们希望调用哪个 `fly` 实现。也可以选择写成 `Human::fly(&person)`,这等同于示例 19-18 中的 `person.fly()`,不过如果无需消歧义的话这么写就有点长了。 +在方法名前指定 trait 名向 Rust 澄清了我们希望调用哪个 `fly` 实现。也可以选择写成 `Human::fly(&person)`,这等同于示例 20-18 中的 `person.fly()`,不过如果无需消歧义的话这么写就有点长了。 运行这段代码会打印出: @@ -144,7 +144,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 因为 `fly` 方法获取一个 `self` 参数,如果有两个 **类型** 都实现了同一 **trait**,Rust 可以根据 `self` 的类型计算出应该使用哪一个 trait 实现。 -然而,并非所有关联函数都有 `self` 参数,比如非方法(non-method)函数。当存在多个类型或者 trait 定义了相同函数名的非方法函数时,Rust 就不总是能计算出我们期望的是哪一个类型,除非使用 **完全限定语法**(*fully qualified syntax*)。例如示例 19-19 中的创建了一个希望将所有小狗叫做 *Spot* 的动物收容所的 trait。`Animal` trait 有一个关联非方法函数 `baby_name`。结构体 `Dog` 实现了 `Animal`,同时又直接提供了关联非方法函数 `baby_name`。 +然而,并非所有关联函数都有 `self` 参数,比如非方法(non-method)函数。当存在多个类型或者 trait 定义了相同函数名的非方法函数时,Rust 就不总是能计算出我们期望的是哪一个类型,除非使用 **完全限定语法**(*fully qualified syntax*)。例如示例 20-19 中的创建了一个希望将所有小狗叫做 *Spot* 的动物收容所的 trait。`Animal` trait 有一个关联非方法函数 `baby_name`。结构体 `Dog` 实现了 `Animal`,同时又直接提供了关联非方法函数 `baby_name`。 文件名:src/main.rs @@ -152,7 +152,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-19/src/main.rs}} ``` -示例 19-19: 一个带有关联函数的 trait 和一个带有同名关联函数并实现了此 trait 的类型 +示例 20-19: 一个带有关联函数的 trait 和一个带有同名关联函数并实现了此 trait 的类型 `Dog` 上定义的关联函数 `baby_name` 的实现代码将所有的小狗起名为 Spot。`Dog` 类型还实现了 `Animal` trait,它描述了所有动物的共有的特征。小狗被称为 puppy,这表现为 `Dog` 的 `Animal` trait 实现中与 `Animal` trait 相关联的函数 `baby_name`。 @@ -162,7 +162,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 {{#include ../listings/ch20-advanced-features/listing-20-19/output.txt}} ``` -这并不是我们需要的。我们希望调用的是 `Dog` 上 `Animal` trait 实现那部分的 `baby_name` 函数,这样能够打印出 `A baby dog is called a puppy`。示例 19-18 中用到的技术在这并不管用;如果将 `main` 改为示例 19-20 中的代码,则会得到一个编译错误: +这并不是我们需要的。我们希望调用的是 `Dog` 上 `Animal` trait 实现那部分的 `baby_name` 函数,这样能够打印出 `A baby dog is called a puppy`。示例 20-18 中用到的技术在这并不管用;如果将 `main` 改为示例 20-20 中的代码,则会得到一个编译错误: 文件名:src/main.rs @@ -170,7 +170,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-20/src/main.rs:here}} ``` -示例 19-20: 尝试调用 `Animal` trait 的 `baby_name` 函数,不过 Rust 并不知道该使用哪一个实现 +示例 20-20: 尝试调用 `Animal` trait 的 `baby_name` 函数,不过 Rust 并不知道该使用哪一个实现 因为 `Animal::baby_name` 没有 `self` 参数,同时这可能会有其它类型实现了 `Animal` trait,Rust 无法计算出所需的是哪一个 `Animal::baby_name` 实现。我们会得到这个编译错误: @@ -178,7 +178,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 {{#include ../listings/ch20-advanced-features/listing-20-20/output.txt}} ``` -为了消歧义并告诉 Rust 我们希望使用的是 `Dog` 的 `Animal` 实现而不是其它类型的 `Animal` 实现,需要使用 **完全限定语法**,这是调用函数时最为明确的方式。示例 19-21 展示了如何使用完全限定语法: +为了消歧义并告诉 Rust 我们希望使用的是 `Dog` 的 `Animal` 实现而不是其它类型的 `Animal` 实现,需要使用 **完全限定语法**,这是调用函数时最为明确的方式。示例 20-21 展示了如何使用完全限定语法: 文件名:src/main.rs @@ -186,7 +186,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-21/src/main.rs:here}} ``` -示例 19-21: 使用完全限定语法来指定我们希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数 +示例 20-21: 使用完全限定语法来指定我们希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数 我们在尖括号中向 Rust 提供了类型注解,并通过在此函数调用中将 `Dog` 类型当作 `Animal` 对待,来指定希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数。现在这段代码会打印出我们期望的数据: @@ -216,7 +216,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 ********** ``` -在 `outline_print` 的实现中,因为希望能够使用 `Display` trait 的功能,则需要说明 `OutlinePrint` 只能用于同时也实现了 `Display` 并提供了 `OutlinePrint` 需要的功能的类型。可以通过在 trait 定义中指定 `OutlinePrint: Display` 来做到这一点。这类似于为 trait 增加 trait bound。示例 19-22 展示了一个 `OutlinePrint` trait 的实现: +在 `outline_print` 的实现中,因为希望能够使用 `Display` trait 的功能,则需要说明 `OutlinePrint` 只能用于同时也实现了 `Display` 并提供了 `OutlinePrint` 需要的功能的类型。可以通过在 trait 定义中指定 `OutlinePrint: Display` 来做到这一点。这类似于为 trait 增加 trait bound。示例 20-22 展示了一个 `OutlinePrint` trait 的实现: 文件名:src/main.rs @@ -224,7 +224,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-22/src/main.rs:here}} ``` -示例 19-22: 实现 `OutlinePrint` trait,它要求来自 `Display` 的功能 +示例 20-22: 实现 `OutlinePrint` trait,它要求来自 `Display` 的功能 因为指定了 `OutlinePrint` 需要 `Display` trait,则可以在 `outline_print` 中使用 `to_string`,其会为任何实现 `Display` 的类型自动实现。如果不在 trait 名后增加 `: Display` 并尝试在 `outline_print` 中使用 `to_string`,则会得到一个错误说在当前作用域中没有找到用于 `&Self` 类型的方法 `to_string`。 @@ -256,7 +256,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 在第十章的 [“为类型实现 trait”][implementing-a-trait-on-a-type] 部分,我们提到了孤儿规则(orphan rule),它说明只有 trait 或类型对于当前 crate 是本地时,才可以在此类型上实现该 trait。一个绕开这个限制的方法是使用 **newtype 模式**(*newtype pattern*),它涉及到在一个元组结构体(第五章 [“用没有命名字段的元组结构体来创建不同的类型”][tuple-structs] 部分介绍了元组结构体)中创建一个新类型。这个元组结构体带有一个字段作为希望实现 trait 的类型的简单封装。接着这个封装类型对于 crate 是本地的,这样就可以在这个封装上实现 trait。*Newtype* 是一个源自 Haskell 编程语言的概念。使用这个模式没有运行时性能惩罚,这个封装类型在编译时就被省略了。 -例如,如果想要在 `Vec` 上实现 `Display`,而孤儿规则阻止我们直接这么做,因为 `Display` trait 和 `Vec` 都定义于我们的 crate 之外。可以创建一个包含 `Vec` 实例的 `Wrapper` 结构体,接着可以如列表 19-23 那样在 `Wrapper` 上实现 `Display` 并使用 `Vec` 的值: +例如,如果想要在 `Vec` 上实现 `Display`,而孤儿规则阻止我们直接这么做,因为 `Display` trait 和 `Vec` 都定义于我们的 crate 之外。可以创建一个包含 `Vec` 实例的 `Wrapper` 结构体,接着可以如列表 20-23 那样在 `Wrapper` 上实现 `Display` 并使用 `Vec` 的值: 文件名:src/main.rs @@ -264,7 +264,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 {{#rustdoc_include ../listings/ch20-advanced-features/listing-20-23/src/main.rs}} ``` -示例 19-23: 创建 `Wrapper` 类型封装 `Vec` 以便能够实现 `Display` +示例 20-23: 创建 `Wrapper` 类型封装 `Vec` 以便能够实现 `Display` `Display` 的实现使用 `self.0` 来访问其内部的 `Vec`,因为 `Wrapper` 是元组结构体而 `Vec` 是结构体总位于索引 0 的项。接着就可以使用 `Wrapper` 中 `Display` 的功能了。 diff --git a/src/ch20-04-advanced-types.md b/src/ch20-03-advanced-types.md similarity index 99% rename from src/ch20-04-advanced-types.md rename to src/ch20-03-advanced-types.md index 92045af..3c742b4 100644 --- a/src/ch20-04-advanced-types.md +++ b/src/ch20-03-advanced-types.md @@ -1,6 +1,6 @@ ## 高级类型 -> [ch20-04-advanced-types.md](https://github.com/rust-lang/book/blob/main/src/ch20-04-advanced-types.md) +> [ch20-03-advanced-types.md](https://github.com/rust-lang/book/blob/main/src/ch20-03-advanced-types.md) >
> commit 95e931170404cb98d476b19017cbbdbc00d0834d diff --git a/src/ch20-05-advanced-functions-and-closures.md b/src/ch20-04-advanced-functions-and-closures.md similarity index 97% rename from src/ch20-05-advanced-functions-and-closures.md rename to src/ch20-04-advanced-functions-and-closures.md index 8bfc2cb..ddd1fe2 100644 --- a/src/ch20-05-advanced-functions-and-closures.md +++ b/src/ch20-04-advanced-functions-and-closures.md @@ -1,6 +1,6 @@ ## 高级函数与闭包 -> [ch20-05-advanced-functions-and-closures.md](https://github.com/rust-lang/book/blob/main/src/ch20-05-advanced-functions-and-closures.md) +> [ch20-04-advanced-functions-and-closures.md](https://github.com/rust-lang/book/blob/main/src/ch20-04-advanced-functions-and-closures.md) >
> commit 21cf840842bdf768a798869f06373c96c1cc5122 diff --git a/src/ch20-06-macros.md b/src/ch20-05-macros.md similarity index 99% rename from src/ch20-06-macros.md rename to src/ch20-05-macros.md index f4b15a3..8c1bbfd 100644 --- a/src/ch20-06-macros.md +++ b/src/ch20-05-macros.md @@ -1,6 +1,6 @@ ## 宏 -> [ch20-06-macros.md](https://github.com/rust-lang/book/blob/main/src/ch20-06-macros.md) +> [ch20-05-macros.md](https://github.com/rust-lang/book/blob/main/src/ch20-05-macros.md) >
> commit 95e931170404cb98d476b19017cbbdbc00d0834d diff --git a/src/ch21-03-graceful-shutdown-and-cleanup.md b/src/ch21-03-graceful-shutdown-and-cleanup.md index ebbd784..51dddbd 100644 --- a/src/ch21-03-graceful-shutdown-and-cleanup.md +++ b/src/ch21-03-graceful-shutdown-and-cleanup.md @@ -34,8 +34,8 @@ 文件名:src/lib.rs -```rust,ignore,does_not_compile -{{#rustdoc_include ../listings/ch21-web-server/no-listing-04-update-worker-definition/src/lib.rs:here}} +```rust +{{#rustdoc_include ../listings/ch21-web-server/no-listing-04-update-drop-definition/src/lib.rs:here}} ``` 现在依靠编译器来找出其他需要修改的地方。check 代码会得到两个错误: