From 61292d171196d57bcec9e7a81d0bf3d8060087f2 Mon Sep 17 00:00:00 2001 From: KaiserY Date: Thu, 10 Feb 2022 16:52:17 +0800 Subject: [PATCH] update to ch19-06 --- src/ch19-01-unsafe-rust.md | 154 ++------- src/ch19-03-advanced-traits.md | 309 +++--------------- src/ch19-04-advanced-types.md | 118 ++----- ...ch19-05-advanced-functions-and-closures.md | 62 +--- src/ch19-06-macros.md | 123 ++----- 5 files changed, 117 insertions(+), 649 deletions(-) diff --git a/src/ch19-01-unsafe-rust.md b/src/ch19-01-unsafe-rust.md index f07a7c5..428942a 100644 --- a/src/ch19-01-unsafe-rust.md +++ b/src/ch19-01-unsafe-rust.md @@ -2,7 +2,7 @@ > [ch19-01-unsafe-rust.md](https://github.com/rust-lang/book/blob/main/src/ch19-01-unsafe-rust.md) >
-> commit 4921fde29ae8ccf67d5893d4e43d74284626fded +> commit 1524fa89fbaa4d52c4a2095141f6eaa6c22f8bd0 目前为止讨论过的代码都有 Rust 在编译时会强制执行的内存安全保证。然而,Rust 还隐藏有第二种语言,它不会强制执行这类内存安全保证:这被称为 **不安全 Rust**(*unsafe Rust*)。它与常规 Rust 代码无异,但是会提供额外的超能力。 @@ -48,10 +48,7 @@ 示例 19-1 展示了如何从引用同时创建不可变和可变裸指针。 ```rust -let mut num = 5; - -let r1 = &num as *const i32; -let r2 = &mut num as *mut i32; +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-01/src/main.rs:here}} ``` 示例 19-1: 通过引用创建裸指针 @@ -63,8 +60,7 @@ let r2 = &mut num as *mut i32; 接下来会创建一个不能确定其有效性的裸指针,示例 19-2 展示了如何创建一个指向任意内存地址的裸指针。尝试使用任意内存是未定义行为:此地址可能有数据也可能没有,编译器可能会优化掉这个内存访问,或者程序可能会出现段错误(segmentation fault)。通常没有好的理由编写这样的代码,不过却是可行的: ```rust -let address = 0x012345usize; -let r = address as *const i32; +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-02/src/main.rs:here}} ``` 示例 19-2: 创建指向任意内存地址的裸指针 @@ -72,15 +68,7 @@ let r = address as *const i32; 记得我们说过可以在安全代码中创建裸指针,不过不能 **解引用** 裸指针和读取其指向的数据。现在我们要做的就是对裸指针使用解引用运算符 `*`,这需要一个 `unsafe` 块,如示例 19-3 所示: ```rust -let mut num = 5; - -let r1 = &num as *const i32; -let r2 = &mut num as *mut i32; - -unsafe { - println!("r1 is: {}", *r1); - println!("r2 is: {}", *r2); -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-03/src/main.rs:here}} ``` 示例 19-3: 在 `unsafe` 块中解引用裸指针 @@ -98,32 +86,13 @@ unsafe { 如下是一个没有做任何操作的不安全函数 `dangerous` 的例子: ```rust -unsafe fn dangerous() {} - -unsafe { - dangerous(); -} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-01-unsafe-fn/src/main.rs:here}} ``` 必须在一个单独的 `unsafe` 块中调用 `dangerous` 函数。如果尝试不使用 `unsafe` 块调用 `dangerous`,则会得到一个错误: ```console -$ cargo run - Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example) -error[E0133]: call to unsafe function is unsafe and requires unsafe function or block - --> src/main.rs:4:5 - | -4 | dangerous(); - | ^^^^^^^^^^^ call to unsafe function - | - = note: consult the function's documentation for information on how to avoid undefined behavior - -error: aborting due to previous error - -For more information about this error, try `rustc --explain E0133`. -error: could not compile `unsafe-example` - -To learn more, run the command again with --verbose. +{{#include ../listings/ch19-advanced-features/output-only-01-missing-unsafe/output.txt}} ``` 通过将 `dangerous` 调用插入 `unsafe` 块中,我们就向 Rust 保证了我们已经阅读过函数的文档,理解如何正确使用,并验证过其满足函数的契约。 @@ -135,14 +104,7 @@ To learn more, run the command again with --verbose. 仅仅因为函数包含不安全代码并不意味着整个函数都需要标记为不安全的。事实上,将不安全代码封装进安全函数是一个常见的抽象。作为一个例子,标准库中的函数,`split_at_mut`,它需要一些不安全代码,让我们探索如何可以实现它。这个安全函数定义于可变 slice 之上:它获取一个 slice 并从给定的索引参数开始将其分为两个 slice。`split_at_mut` 的用法如示例 19-4 所示: ```rust -let mut v = vec![1, 2, 3, 4, 5, 6]; - -let r = &mut v[..]; - -let (a, b) = r.split_at_mut(3); - -assert_eq!(a, &mut [1, 2, 3]); -assert_eq!(b, &mut [4, 5, 6]); +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-04/src/main.rs:here}} ``` 示例 19-4: 使用安全的 `split_at_mut` 函数 @@ -150,14 +112,7 @@ assert_eq!(b, &mut [4, 5, 6]); 这个函数无法只通过安全 Rust 实现。一个尝试可能看起来像示例 19-5,它不能编译。出于简单考虑,我们将 `split_at_mut` 实现为函数而不是方法,并只处理 `i32` 值而非泛型 `T` 的 slice。 ```rust,ignore,does_not_compile -fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { - let len = slice.len(); - - assert!(mid <= len); - - (&mut slice[..mid], - &mut slice[mid..]) -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-05/src/main.rs:here}} ``` 示例 19-5: 尝试只使用安全 Rust 来实现 `split_at_mut` @@ -169,27 +124,7 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { 如果尝试编译示例 19-5 的代码,会得到一个错误: ```console -$ cargo run - Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example) -error[E0499]: cannot borrow `*slice` as mutable more than once at a time - --> src/main.rs:6:30 - | -1 | fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { - | - let's call the lifetime of this reference `'1` -... -6 | (&mut slice[..mid], &mut slice[mid..]) - | -------------------------^^^^^-------- - | | | | - | | | second mutable borrow occurs here - | | first mutable borrow occurs here - | returning this value requires that `*slice` is borrowed for `'1` - -error: aborting due to previous error - -For more information about this error, try `rustc --explain E0499`. -error: could not compile `unsafe-example` - -To learn more, run the command again with --verbose. +{{#include ../listings/ch19-advanced-features/listing-19-05/output.txt}} ``` Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同部分:它只知道我们借用了同一个 slice 两次。本质上借用 slice 的不同部分是可以的,因为结果两个 slice 不会重叠,不过 Rust 还没有智能到能够理解这些。当我们知道某些事是可以的而 Rust 不知道的时候,就是触及不安全代码的时候了 @@ -197,19 +132,7 @@ Rust 的借用检查器不能理解我们要借用这个 slice 的两个不同 示例 19-6 展示了如何使用 `unsafe` 块,裸指针和一些不安全函数调用来实现 `split_at_mut`: ```rust -use std::slice; - -fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { - let len = slice.len(); - let ptr = slice.as_mut_ptr(); - - assert!(mid <= len); - - unsafe { - (slice::from_raw_parts_mut(ptr, mid), - slice::from_raw_parts_mut(ptr.add(mid), len - mid)) - } -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-06/src/main.rs:here}} ``` 示例 19-6: 在 `split_at_mut` 函数的实现中使用不安全代码 @@ -225,14 +148,7 @@ fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { 与此相对,示例 19-7 中的 `slice::from_raw_parts_mut` 在使用 slice 时很有可能会崩溃。这段代码获取任意内存地址并创建了一个长为一万的 slice: ```rust -use std::slice; - -let address = 0x01234usize; -let r = address as *mut i32; - -let slice: &[i32] = unsafe { - slice::from_raw_parts_mut(r, 10000) -}; +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-07/src/main.rs:here}} ``` 示例 19-7: 通过任意内存地址创建 slice @@ -248,15 +164,7 @@ let slice: &[i32] = unsafe { 文件名: src/main.rs ```rust -extern "C" { - fn abs(input: i32) -> i32; -} - -fn main() { - unsafe { - println!("Absolute value of -3 according to C: {}", abs(-3)); - } -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-08/src/main.rs}} ``` 示例 19-8: 声明并调用另一个语言中定义的 `extern` 函数 @@ -287,11 +195,7 @@ fn main() { 文件名: src/main.rs ```rust -static HELLO_WORLD: &str = "Hello, world!"; - -fn main() { - println!("name is: {}", HELLO_WORLD); -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-09/src/main.rs}} ``` 示例 19-9: 定义和使用一个不可变静态变量 @@ -305,21 +209,7 @@ fn main() { 文件名: src/main.rs ```rust -static mut COUNTER: u32 = 0; - -fn add_to_count(inc: u32) { - unsafe { - COUNTER += inc; - } -} - -fn main() { - add_to_count(3); - - unsafe { - println!("COUNTER: {}", COUNTER); - } -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-10/src/main.rs}} ``` 示例 19-10: 读取或修改一个可变静态变量是不安全的 @@ -333,13 +223,7 @@ fn main() { `unsafe` 的另一个操作用例是实现不安全 trait。当 trait 中至少有一个方法中包含编译器无法验证的不变式(invariant)时 trait 是不安全的。可以在 `trait` 之前增加 `unsafe` 关键字将 trait 声明为 `unsafe`,同时 trait 的实现也必须标记为 `unsafe`,如示例 19-11 所示: ```rust -unsafe trait Foo { - // methods go here -} - -unsafe impl Foo for i32 { - // method implementations go here -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-11/src/main.rs}} ``` 示例 19-11: 定义并实现不安全 trait @@ -350,17 +234,17 @@ unsafe impl Foo for i32 { ### 访问联合体中的字段 -仅适用于 `unsafe` 的最后一个操作是访问 **联合体** 中的字段,`union` 和 `struct` 类似,但是在一个实例中同时只能使用一个声明的字段。联合体主要用于和 C 代码中的联合体交互。访问联合体的字段是不安全的,因为 Rust 无法保证当前存储在联合体实例中数据的类型。可以查看[参考文档][reference]了解有关联合体的更多信息。 +仅适用于 `unsafe` 的最后一个操作是访问 **联合体** 中的字段,`union` 和 `struct` 类似,但是在一个实例中同时只能使用一个声明的字段。联合体主要用于和 C 代码中的联合体交互。访问联合体的字段是不安全的,因为 Rust 无法保证当前存储在联合体实例中数据的类型。可以查看 [参考文档][reference] 了解有关联合体的更多信息。 ### 何时使用不安全代码 使用 `unsafe` 来进行这五个操作(超能力)之一是没有问题的,甚至是不需要深思熟虑的,不过使得 `unsafe` 代码正确也实属不易,因为编译器不能帮助保证内存安全。当有理由使用 `unsafe` 代码时,是可以这么做的,通过使用显式的 `unsafe` 标注可以更容易地在错误发生时追踪问题的源头。 [dangling-references]: -ch04-02-references-and-borrowing.html#dangling-references +ch04-02-references-and-borrowing.html#悬垂引用dangling-references [differences-between-variables-and-constants]: -ch03-01-variables-and-mutability.html#变量和常量的区别 +ch03-01-variables-and-mutability.html#常量 [extensible-concurrency-with-the-sync-and-send-traits]: ch16-04-extensible-concurrency-sync-and-send.html#使用-sync-和-send-trait-的可扩展并发 -[the-slice-type]: ch04-03-slices.html#the-slice-type +[the-slice-type]: ch04-03-slices.html#slice-类型 [reference]: https://doc.rust-lang.org/reference/items/unions.html diff --git a/src/ch19-03-advanced-traits.md b/src/ch19-03-advanced-traits.md index a1c0fa2..1355552 100644 --- a/src/ch19-03-advanced-traits.md +++ b/src/ch19-03-advanced-traits.md @@ -2,7 +2,7 @@ > [ch19-03-advanced-traits.md](https://github.com/rust-lang/book/blob/main/src/ch19-03-advanced-traits.md) >
-> commit 426f3e4ec17e539ae9905ba559411169d303a031 +> commit 81d05c9a6d06d79f2a85c8ea184f41dc82532d98 第十章 [“trait:定义共享的行为”][traits-defining-shared-behavior] 部分,我们第一次涉及到了 trait,不过就像生命周期一样,我们并没有覆盖一些较为高级的细节。现在我们更加了解 Rust 了,可以深入理解其本质了。 @@ -14,12 +14,8 @@ 一个带有关联类型的 trait 的例子是标准库提供的 `Iterator` trait。它有一个叫做 `Item` 的关联类型来替代遍历的值的类型。第十三章的 [“`Iterator` trait 和 `next` 方法”][the-iterator-trait-and-the-next-method] 部分曾提到过 `Iterator` trait 的定义如示例 19-12 所示: -```rust -pub trait Iterator { - type Item; - - fn next(&mut self) -> Option; -} +```rust,noplayground +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-12/src/lib.rs}} ``` 示例 19-12: `Iterator` trait 的定义中带有关联类型 `Item` @@ -33,19 +29,13 @@ pub trait Iterator { 文件名: src/lib.rs ```rust,ignore -impl Iterator for Counter { - type Item = u32; - - fn next(&mut self) -> Option { - // --snip-- +{{#rustdoc_include ../listings/ch19-advanced-features/listing-13-21-reproduced/src/lib.rs:ch19}} ``` 这类似于泛型。那么为什么 `Iterator` trait 不像示例 19-13 那样定义呢? -```rust -pub trait Iterator { - fn next(&mut self) -> Option; -} +```rust,noplayground +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-13/src/lib.rs}} ``` 示例 19-13: 一个使用泛型的 `Iterator` trait 假想定义 @@ -65,29 +55,7 @@ Rust 并不允许创建自定义运算符或重载任意运算符,不过 `std: 文件名: src/main.rs ```rust -use std::ops::Add; - -#[derive(Debug, PartialEq)] -struct Point { - x: i32, - y: i32, -} - -impl Add for Point { - type Output = Point; - - fn add(self, other: Point) -> Point { - Point { - x: self.x + other.x, - y: self.y + other.y, - } - } -} - -fn main() { - assert_eq!(Point { x: 1, y: 0 } + Point { x: 2, y: 3 }, - Point { x: 3, y: 3 }); -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-14/src/main.rs}} ``` 示例 19-14: 实现 `Add` trait 重载 `Point` 实例的 `+` 运算符 @@ -97,39 +65,28 @@ fn main() { 这里默认泛型类型位于 `Add` trait 中。这里是其定义: ```rust -trait Add { +trait Add { type Output; - fn add(self, rhs: RHS) -> Self::Output; + fn add(self, rhs: Rhs) -> Self::Output; } ``` -这看来应该很熟悉,这是一个带有一个方法和一个关联类型的 trait。比较陌生的部分是尖括号中的 `RHS=Self`:这个语法叫做 **默认类型参数**(*default type parameters*)。`RHS` 是一个泛型类型参数(“right hand side” 的缩写),它用于定义 `add` 方法中的 `rhs` 参数。如果实现 `Add` trait 时不指定 `RHS` 的具体类型,`RHS` 的类型将是默认的 `Self` 类型,也就是在其上实现 `Add` 的类型。 +这些代码看来应该很熟悉,这是一个带有一个方法和一个关联类型的 trait。比较陌生的部分是尖括号中的 `Rhs=Self`:这个语法叫做 **默认类型参数**(*default type parameters*)。`Rhs` 是一个泛型类型参数(“right hand side” 的缩写),它用于定义 `add` 方法中的 `rhs` 参数。如果实现 `Add` trait 时不指定 `Rhs` 的具体类型,`Rhs` 的类型将是默认的 `Self` 类型,也就是在其上实现 `Add` 的类型。 -当为 `Point` 实现 `Add` 时,使用了默认的 `RHS`,因为我们希望将两个 `Point` 实例相加。让我们看看一个实现 `Add` trait 时希望自定义 `RHS` 类型而不是使用默认类型的例子。 +当为 `Point` 实现 `Add` 时,使用了默认的 `Rhs`,因为我们希望将两个 `Point` 实例相加。让我们看看一个实现 `Add` trait 时希望自定义 `Rhs` 类型而不是使用默认类型的例子。 -这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters`。我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `RHS`,如示例 19-15 所示。 +这里有两个存放不同单元值的结构体,`Millimeters` 和 `Meters`。(这种将现有类型简单封装进另一个结构体的方式被称为 **newtype 模式**(*newtype pattern*,之后的 [“为了类型安全和抽象而使用 newtype 模式”][newtype] 部分会详细介绍。)我们希望能够将毫米值与米值相加,并让 `Add` 的实现正确处理转换。可以为 `Millimeters` 实现 `Add` 并以 `Meters` 作为 `Rhs`,如示例 19-15 所示。 文件名: src/lib.rs -```rust -use std::ops::Add; - -struct Millimeters(u32); -struct Meters(u32); - -impl Add for Millimeters { - type Output = Millimeters; - - fn add(self, other: Meters) -> Millimeters { - Millimeters(self.0 + (other.0 * 1000)) - } -} +```rust,noplayground +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-15/src/lib.rs}} ``` 示例 19-15: 在 `Millimeters` 上实现 `Add`,以便能够将 `Millimeters` 与 `Meters` 相加 -为了使 `Millimeters` 和 `Meters` 能够相加,我们指定 `impl Add` 来设定 `RHS` 类型参数的值而不是使用默认的 `Self`。 +为了使 `Millimeters` 和 `Meters` 能够相加,我们指定 `impl Add` 来设定 `Rhs` 类型参数的值而不是使用默认的 `Self`。 默认参数类型主要用于如下两个方面: @@ -149,33 +106,7 @@ Rust 既不能避免一个 trait 与另一个 trait 拥有相同名称的方法 文件名: src/main.rs ```rust -trait Pilot { - fn fly(&self); -} - -trait Wizard { - fn fly(&self); -} - -struct Human; - -impl Pilot for Human { - fn fly(&self) { - println!("This is your captain speaking."); - } -} - -impl Wizard for Human { - fn fly(&self) { - println!("Up!"); - } -} - -impl Human { - fn fly(&self) { - println!("*waving arms furiously*"); - } -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-16/src/main.rs:here}} ``` 示例 19-16: 两个 trait 定义为拥有 `fly` 方法,并在直接定义有 `fly` 方法的 `Human` 类型上实现这两个 trait @@ -185,38 +116,7 @@ impl Human { 文件名: src/main.rs ```rust -# trait Pilot { -# fn fly(&self); -# } -# -# trait Wizard { -# fn fly(&self); -# } -# -# struct Human; -# -# impl Pilot for Human { -# fn fly(&self) { -# println!("This is your captain speaking."); -# } -# } -# -# impl Wizard for Human { -# fn fly(&self) { -# println!("Up!"); -# } -# } -# -# impl Human { -# fn fly(&self) { -# println!("*waving arms furiously*"); -# } -# } -# -fn main() { - let person = Human; - person.fly(); -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-17/src/main.rs:here}} ``` 示例 19-17: 调用 `Human` 实例的 `fly` @@ -228,40 +128,7 @@ fn main() { 文件名: src/main.rs ```rust -# trait Pilot { -# fn fly(&self); -# } -# -# trait Wizard { -# fn fly(&self); -# } -# -# struct Human; -# -# impl Pilot for Human { -# fn fly(&self) { -# println!("This is your captain speaking."); -# } -# } -# -# impl Wizard for Human { -# fn fly(&self) { -# println!("Up!"); -# } -# } -# -# impl Human { -# fn fly(&self) { -# println!("*waving arms furiously*"); -# } -# } -# -fn main() { - let person = Human; - Pilot::fly(&person); - Wizard::fly(&person); - person.fly(); -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-18/src/main.rs:here}} ``` 示例 19-18: 指定我们希望调用哪一个 trait 的 `fly` 方法 @@ -270,10 +137,8 @@ fn main() { 运行这段代码会打印出: -```text -This is your captain speaking. -Up! -*waving arms furiously* +```console +{{#include ../listings/ch19-advanced-features/listing-19-18/output.txt}} ``` 因为 `fly` 方法获取一个 `self` 参数,如果有两个 **类型** 都实现了同一 **trait**,Rust 可以根据 `self` 的类型计算出应该使用哪一个 trait 实现。 @@ -283,27 +148,7 @@ Up! 文件名: src/main.rs ```rust -trait Animal { - fn baby_name() -> String; -} - -struct Dog; - -impl Dog { - fn baby_name() -> String { - String::from("Spot") - } -} - -impl Animal for Dog { - fn baby_name() -> String { - String::from("puppy") - } -} - -fn main() { - println!("A baby dog is called a {}", Dog::baby_name()); -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-19/src/main.rs}} ``` 示例 19-19: 一个带有关联函数的 trait 和一个带有同名关联函数并实现了此 trait 的类型 @@ -312,8 +157,8 @@ fn main() { 在 `main` 调用了 `Dog::baby_name` 函数,它直接调用了定义于 `Dog` 之上的关联函数。这段代码会打印出: -```text -A baby dog is called a Spot +```console +{{#include ../listings/ch19-advanced-features/listing-19-19/output.txt}} ``` 这并不是我们需要的。我们希望调用的是 `Dog` 上 `Animal` trait 实现那部分的 `baby_name` 函数,这样能够打印出 `A baby dog is called a puppy`。示例 19-18 中用到的技术在这并不管用;如果将 `main` 改为示例 19-20 中的代码,则会得到一个编译错误: @@ -321,23 +166,15 @@ A baby dog is called a Spot 文件名: src/main.rs ```rust,ignore,does_not_compile -fn main() { - println!("A baby dog is called a {}", Animal::baby_name()); -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-20/src/main.rs:here}} ``` 示例 19-20: 尝试调用 `Animal` trait 的 `baby_name` 函数,不过 Rust 并不知道该使用哪一个实现 因为 `Animal::baby_name` 是关联函数而不是方法,因此它没有 `self` 参数,Rust 无法计算出所需的是哪一个 `Animal::baby_name` 实现。我们会得到这个编译错误: -```text -error[E0283]: type annotations required: cannot resolve `_: Animal` - --> src/main.rs:20:43 - | -20 | println!("A baby dog is called a {}", Animal::baby_name()); - | ^^^^^^^^^^^^^^^^^ - | - = note: required by `Animal::baby_name` +```console +{{#include ../listings/ch19-advanced-features/listing-19-20/output.txt}} ``` 为了消歧义并告诉 Rust 我们希望使用的是 `Dog` 的 `Animal` 实现,需要使用 **完全限定语法**,这是调用函数时最为明确的方式。示例 19-21 展示了如何使用完全限定语法: @@ -345,35 +182,15 @@ error[E0283]: type annotations required: cannot resolve `_: Animal` 文件名: src/main.rs ```rust -# trait Animal { -# fn baby_name() -> String; -# } -# -# struct Dog; -# -# impl Dog { -# fn baby_name() -> String { -# String::from("Spot") -# } -# } -# -# impl Animal for Dog { -# fn baby_name() -> String { -# String::from("puppy") -# } -# } -# -fn main() { - println!("A baby dog is called a {}", ::baby_name()); -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-21/src/main.rs:here}} ``` 示例 19-21: 使用完全限定语法来指定我们希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数 我们在尖括号中向 Rust 提供了类型注解,并通过在此函数调用中将 `Dog` 类型当作 `Animal` 对待,来指定希望调用的是 `Dog` 上 `Animal` trait 实现中的 `baby_name` 函数。现在这段代码会打印出我们期望的数据: -```text -A baby dog is called a puppy +```console +{{#include ../listings/ch19-advanced-features/listing-19-21/output.txt}} ``` 通常,完全限定语法定义为: @@ -403,19 +220,7 @@ A baby dog is called a puppy 文件名: src/main.rs ```rust -use std::fmt; - -trait OutlinePrint: fmt::Display { - fn outline_print(&self) { - let output = self.to_string(); - let len = output.len(); - println!("{}", "*".repeat(len + 4)); - println!("*{}*", " ".repeat(len + 2)); - println!("* {} *", output); - println!("*{}*", " ".repeat(len + 2)); - println!("{}", "*".repeat(len + 4)); - } -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-22/src/main.rs:here}} ``` 示例 19-22: 实现 `OutlinePrint` trait,它要求来自 `Display` 的功能 @@ -426,27 +231,14 @@ trait OutlinePrint: fmt::Display { 文件名: src/main.rs -```rust -# trait OutlinePrint {} -struct Point { - x: i32, - y: i32, -} - -impl OutlinePrint for Point {} +```rust,ignore,does_not_compile +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-02-impl-outlineprint-for-point/src/main.rs:here}} ``` 这样会得到一个错误说 `Display` 是必须的而未被实现: -```text -error[E0277]: the trait bound `Point: std::fmt::Display` is not satisfied - --> src/main.rs:20:6 - | -20 | impl OutlinePrint for Point {} - | ^^^^^^^^^^^^ `Point` cannot be formatted with the default formatter; -try using `:?` instead if you are using a format string - | - = help: the trait `std::fmt::Display` is not implemented for `Point` +```console +{{#include ../listings/ch19-advanced-features/no-listing-02-impl-outlineprint-for-point/output.txt}} ``` 一旦在 `Point` 上实现 `Display` 并满足 `OutlinePrint` 要求的限制,比如这样: @@ -454,18 +246,7 @@ try using `:?` instead if you are using a format string 文件名: src/main.rs ```rust -# struct Point { -# x: i32, -# y: i32, -# } -# -use std::fmt; - -impl fmt::Display for Point { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "({}, {})", self.x, self.y) - } -} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-03-impl-display-for-point/src/main.rs:here}} ``` 那么在 `Point` 上实现 `OutlinePrint` trait 将能成功编译,并可以在 `Point` 实例上调用 `outline_print` 来显示位于星号框中的点的值。 @@ -474,28 +255,15 @@ impl fmt::Display for Point { 在第十章的 [“为类型实现 trait”][implementing-a-trait-on-a-type] 部分,我们提到了孤儿规则(orphan rule),它说明只要 trait 或类型对于当前 crate 是本地的话就可以在此类型上实现该 trait。一个绕开这个限制的方法是使用 **newtype 模式**(*newtype pattern*),它涉及到在一个元组结构体(第五章 [“用没有命名字段的元组结构体来创建不同的类型”][tuple-structs] 部分介绍了元组结构体)中创建一个新类型。这个元组结构体带有一个字段作为希望实现 trait 的类型的简单封装。接着这个封装类型对于 crate 是本地的,这样就可以在这个封装上实现 trait。*Newtype* 是一个源自 ~~(U.C.0079,逃)~~ Haskell 编程语言的概念。使用这个模式没有运行时性能惩罚,这个封装类型在编译时就被省略了。 -例如,如果想要在 `Vec` 上实现 `Display`,而孤儿规则阻止我们直接这么做,因为 `Display` trait 和 `Vec` 都定义于我们的 crate 之外。可以创建一个包含 `Vec` 实例的 `Wrapper` 结构体,接着可以如列表 19-31 那样在 `Wrapper` 上实现 `Display` 并使用 `Vec` 的值: +例如,如果想要在 `Vec` 上实现 `Display`,而孤儿规则阻止我们直接这么做,因为 `Display` trait 和 `Vec` 都定义于我们的 crate 之外。可以创建一个包含 `Vec` 实例的 `Wrapper` 结构体,接着可以如列表 19-23 那样在 `Wrapper` 上实现 `Display` 并使用 `Vec` 的值: 文件名: src/main.rs ```rust -use std::fmt; - -struct Wrapper(Vec); - -impl fmt::Display for Wrapper { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "[{}]", self.0.join(", ")) - } -} - -fn main() { - let w = Wrapper(vec![String::from("hello"), String::from("world")]); - println!("w = {}", w); -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-23/src/main.rs}} ``` -示例 19-31: 创建 `Wrapper` 类型封装 `Vec` 以便能够实现 `Display` +示例 19-23: 创建 `Wrapper` 类型封装 `Vec` 以便能够实现 `Display` `Display` 的实现使用 `self.0` 来访问其内部的 `Vec`,因为 `Wrapper` 是元组结构体而 `Vec` 是结构体总位于索引 0 的项。接着就可以使用 `Wrapper` 中 `Display` 的功能了。 @@ -503,6 +271,7 @@ fn main() { 上面便是 newtype 模式如何与 trait 结合使用的;还有一个不涉及 trait 的实用模式。现在让我们将话题的焦点转移到一些与 Rust 类型系统交互的高级方法上来吧。 +[newtype]: ch19-04-advanced-types.html#为了类型安全和抽象而使用-newtype-模式 [implementing-a-trait-on-a-type]: ch10-02-traits.html#为类型实现-trait [the-iterator-trait-and-the-next-method]: ch13-02-iterators.html#iterator-trait-和-next-方法 [traits-defining-shared-behavior]: ch10-02-traits.html#trait定义共享的行为 diff --git a/src/ch19-04-advanced-types.md b/src/ch19-04-advanced-types.md index afd575e..a41beb7 100644 --- a/src/ch19-04-advanced-types.md +++ b/src/ch19-04-advanced-types.md @@ -2,7 +2,7 @@ > [ch19-04-advanced-types.md](https://github.com/rust-lang/book/blob/main/src/ch19-04-advanced-types.md) >
-> commit 426f3e4ec17e539ae9905ba559411169d303a031 +> commit a90f07f1e9a7fc75dc9105a6c6f16d5c13edceb0 Rust 的类型系统有一些我们曾经提到但没有讨论过的功能。首先我们从一个关于为什么 newtype 与类型一样有用的更宽泛的讨论开始。接着会转向类型别名(type aliases),一个类似于 newtype 但有着稍微不同的语义的功能。我们还会讨论 `!` 类型和动态大小类型。 @@ -21,18 +21,13 @@ newtype 也可以隐藏其内部的泛型类型。例如,可以提供一个封 连同 newtype 模式,Rust 还提供了声明 **类型别名**(*type alias*)的能力,使用 `type` 关键字来给予现有类型另一个名字。例如,可以像这样创建 `i32` 的别名 `Kilometers`: ```rust -type Kilometers = i32; +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-04-kilometers-alias/src/main.rs:here}} ``` 这意味着 `Kilometers` 是 `i32` 的 **同义词**(*synonym*);不同于示例 19-15 中创建的 `Millimeters` 和 `Meters` 类型。`Kilometers` 不是一个新的、单独的类型。`Kilometers` 类型的值将被完全当作 `i32` 类型值来对待: ```rust -type Kilometers = i32; - -let x: i32 = 5; -let y: Kilometers = 5; - -println!("x + y = {}", x + y); +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-04-kilometers-alias/src/main.rs:there}} ``` 因为 `Kilometers` 是 `i32` 的别名,他们是同一类型,可以将 `i32` 与 `Kilometers` 相加,也可以将 `Kilometers` 传递给获取 `i32` 参数的函数。但通过这种手段无法获得上一部分讨论的 newtype 模式所提供的类型检查的好处。 @@ -46,16 +41,7 @@ Box 在函数签名或类型注解中每次都书写这个类型将是枯燥且易于出错的。想象一下如示例 19-24 这样全是如此代码的项目: ```rust -let f: Box = Box::new(|| println!("hi")); - -fn takes_long_type(f: Box) { - // --snip-- -} - -fn returns_long_type() -> Box { - // --snip-- -# Box::new(|| ()) -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-24/src/main.rs:here}} ``` 示例 19-24: 在很多地方使用名称很长的类型 @@ -63,18 +49,7 @@ fn returns_long_type() -> Box { 类型别名通过减少项目中重复代码的数量来使其更加易于控制。这里我们为这个冗长的类型引入了一个叫做 `Thunk` 的别名,这样就可以如示例 19-25 所示将所有使用这个类型的地方替换为更短的 `Thunk`: ```rust -type Thunk = Box; - -let f: Thunk = Box::new(|| println!("hi")); - -fn takes_long_type(f: Thunk) { - // --snip-- -} - -fn returns_long_type() -> Thunk { - // --snip-- -# Box::new(|| ()) -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-25/src/main.rs:here}} ``` 示例 19-25: 引入类型别名 `Thunk` 来减少重复 @@ -83,35 +58,20 @@ fn returns_long_type() -> Thunk { 类型别名也经常与 `Result` 结合使用来减少重复。考虑一下标准库中的 `std::io` 模块。I/O 操作通常会返回一个 `Result`,因为这些操作可能会失败。标准库中的 `std::io::Error` 结构体代表了所有可能的 I/O 错误。`std::io` 中大部分函数会返回 `Result`,其中 `E` 是 `std::io::Error`,比如 `Write` trait 中的这些函数: -```rust -use std::io::Error; -use std::fmt; - -pub trait Write { - fn write(&mut self, buf: &[u8]) -> Result; - fn flush(&mut self) -> Result<(), Error>; - - fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>; - fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>; -} +```rust,noplayground +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-05-write-trait/src/lib.rs}} ``` 这里出现了很多的 `Result<..., Error>`。为此,`std::io` 有这个类型别名声明: -```rust -type Result = std::result::Result; +```rust,noplayground +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-06-result-alias/src/lib.rs:here}} ``` 因为这位于 `std::io` 中,可用的完全限定的别名是 `std::io::Result` —— 也就是说,`Result` 中 `E` 放入了 `std::io::Error`。`Write` trait 中的函数最终看起来像这样: -```rust,ignore -pub trait Write { - fn write(&mut self, buf: &[u8]) -> Result; - fn flush(&mut self) -> Result<()>; - - fn write_all(&mut self, buf: &[u8]) -> Result<()>; - fn write_fmt(&mut self, fmt: Arguments) -> Result<()>; -} +```rust,noplayground +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-06-result-alias/src/lib.rs:there}} ``` 类型别名在两个方面有帮助:易于编写 **并** 在整个 `std::io` 中提供了一致的接口。因为这是一个别名,它只是另一个 `Result`,这意味着可以在其上使用 `Result` 的任何方法,以及像 `?` 这样的特殊语法。 @@ -120,25 +80,16 @@ pub trait Write { Rust 有一个叫做 `!` 的特殊类型。在类型理论术语中,它被称为 *empty type*,因为它没有值。我们更倾向于称之为 *never type*。这个名字描述了它的作用:在函数从不返回的时候充当返回值。例如: -```rust,ignore -fn bar() -> ! { - // --snip-- -} +```rust,noplayground +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-07-never-type/src/lib.rs:here}} ``` 这读 “函数 `bar` 从不返回”,而从不返回的函数被称为 **发散函数**(*diverging functions*)。不能创建 `!` 类型的值,所以 `bar` 也不可能返回值。 不过一个不能创建值的类型有什么用呢?如果你回想一下示例 2-5 中的代码,曾经有一些看起来像这样的代码,如示例 19-26 所重现的: -```rust -# let guess = "3"; -# loop { -let guess: u32 = match guess.trim().parse() { - Ok(num) => num, - Err(_) => continue, -}; -# break; -# } +```rust,ignore +{{#rustdoc_include ../listings/ch02-guessing-game-tutorial/listing-02-05/src/main.rs:ch19}} ``` 示例 19-26: `match` 语句和一个以 `continue` 结束的分支 @@ -146,10 +97,7 @@ let guess: u32 = match guess.trim().parse() { 当时我们忽略了代码中的一些细节。在第六章 [“`match` 控制流运算符”][the-match-control-flow-operator] 部分,我们学习了 `match` 的分支必须返回相同的类型。如下代码不能工作: ```rust,ignore,does_not_compile -let guess = match guess.trim().parse() { - Ok(_) => 5, - Err(_) => "hello", -} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-08-match-arms-different-types/src/main.rs:here}} ``` 这里的 `guess` 必须既是整型 **也是** 字符串,而 Rust 要求 `guess` 只能是一个类型。那么 `continue` 返回了什么呢?为什么示例 19-26 中会允许一个分支返回 `u32` 而另一个分支却以 `continue` 结束呢? @@ -161,14 +109,7 @@ let guess = match guess.trim().parse() { never type 的另一个用途是 `panic!`。还记得 `Option` 上的 `unwrap` 函数吗?它产生一个值或 panic。这里是它的定义: ```rust,ignore -impl Option { - pub fn unwrap(self) -> T { - match self { - Some(val) => val, - None => panic!("called `Option::unwrap()` on a `None` value"), - } - } -} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-09-unwrap-definition/src/lib.rs:here}} ``` 这里与示例 19-34 中的 `match` 发生了相同的情况:Rust 知道 `val` 是 `T` 类型,`panic!` 是 `!` 类型,所以整个 `match` 表达式的结果是 `T` 类型。这能工作是因为 `panic!` 并不产生一个值;它会终止程序。对于 `None` 的情况,`unwrap` 并不返回一个值,所以这些代码是有效的。 @@ -176,11 +117,7 @@ impl Option { 最后一个有着 `!` 类型的表达式是 `loop`: ```rust,ignore -print!("forever "); - -loop { - print!("and ever "); -} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-10-loop-returns-never/src/main.rs:here}} ``` 这里,循环永远也不结束,所以此表达式的值是 `!`。但是如果引入 `break` 这就不为真了,因为循环在执行到 `break` 后就会终止。 @@ -192,8 +129,7 @@ loop { 让我们深入研究一个贯穿本书都在使用的动态大小类型的细节:`str`。没错,不是 `&str`,而是 `str` 本身。`str` 是一个 DST;直到运行时我们都不知道字符串有多长。因为直到运行时都不能知道其大小,也就意味着不能创建 `str` 类型的变量,也不能获取 `str` 类型的参数。考虑一下这些代码,他们不能工作: ```rust,ignore,does_not_compile -let s1: str = "Hello there!"; -let s2: str = "How's it going?"; +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-11-cant-create-str/src/main.rs:here}} ``` Rust 需要知道应该为特定类型的值分配多少内存,同时所有同一类型的值必须使用相同数量的内存。如果允许编写这样的代码,也就意味着这两个 `str` 需要占用完全相同大小的空间,不过它们有着不同的长度。这也就是为什么不可能创建一个存放动态大小类型的变量的原因。 @@ -207,28 +143,22 @@ Rust 需要知道应该为特定类型的值分配多少内存,同时所有同 为了处理 DST,Rust 有一个特定的 trait 来决定一个类型的大小是否在编译时可知:这就是 `Sized` trait。这个 trait 自动为编译器在编译时就知道大小的类型实现。另外,Rust 隐式的为每一个泛型函数增加了 `Sized` bound。也就是说,对于如下泛型函数定义: ```rust,ignore -fn generic(t: T) { - // --snip-- -} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-12-generic-fn-definition/src/lib.rs}} ``` 实际上被当作如下处理: ```rust,ignore -fn generic(t: T) { - // --snip-- -} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-13-generic-implicit-sized-bound/src/lib.rs}} ``` 泛型函数默认只能用于在编译时已知大小的类型。然而可以使用如下特殊语法来放宽这个限制: ```rust,ignore -fn generic(t: &T) { - // --snip-- -} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-14-generic-maybe-sized/src/lib.rs}} ``` -`?Sized` trait bound 与 `Sized` 相对;也就是说,它可以读作 “`T` 可能是也可能不是 `Sized` 的”。这个语法只能用于 `Sized` ,而不能用于其他 trait。 +`?Sized` 上的 trait bound 意味着 “`T` 可能是也可能不是 `Sized`” 同时这个注解会覆盖泛型类型必须在编译时拥有固定大小的默认规则。这种意义的 `?Trait` 语法只能用于 `Sized` ,而不能用于任何其他 trait。 另外注意我们将 `t` 参数的类型从 `T` 变为了 `&T`:因为其类型可能不是 `Sized` 的,所以需要将其置于某种指针之后。在这个例子中选择了引用。 @@ -236,7 +166,7 @@ fn generic(t: &T) { [encapsulation-that-hides-implementation-details]: ch17-01-what-is-oo.html#封装隐藏了实现细节 -[string-slices]: ch04-03-slices.html#string-slices +[string-slices]: ch04-03-slices.html#字符串-slice [the-match-control-flow-operator]: ch06-02-match.html#match-控制流运算符 [using-trait-objects-that-allow-for-values-of-different-types]: diff --git a/src/ch19-05-advanced-functions-and-closures.md b/src/ch19-05-advanced-functions-and-closures.md index ea00669..61a40e1 100644 --- a/src/ch19-05-advanced-functions-and-closures.md +++ b/src/ch19-05-advanced-functions-and-closures.md @@ -2,7 +2,7 @@ > [ch19-05-advanced-functions-and-closures.md](https://github.com/rust-lang/book/blob/main/src/ch19-05-advanced-functions-and-closures.md) >
-> commit 426f3e4ec17e539ae9905ba559411169d303a031 +> commit 9e30688e0ac4a1ad86fc60aa380bebfb1c34b8a7 接下来我们将探索一些有关函数和闭包的高级功能:函数指针以及返回值闭包。 @@ -13,19 +13,7 @@ 文件名: src/main.rs ```rust -fn add_one(x: i32) -> i32 { - x + 1 -} - -fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 { - f(arg) + f(arg) -} - -fn main() { - let answer = do_twice(add_one, 5); - - println!("The answer is: {}", answer); -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-27/src/main.rs}} ``` 示例 19-27: 使用 `fn` 类型接受函数指针作为参数 @@ -41,21 +29,13 @@ fn main() { 作为一个既可以使用内联定义的闭包又可以使用命名函数的例子,让我们看看一个 `map` 的应用。使用 `map` 函数将一个数字 vector 转换为一个字符串 vector,就可以使用闭包,比如这样: ```rust -let list_of_numbers = vec![1, 2, 3]; -let list_of_strings: Vec = list_of_numbers - .iter() - .map(|i| i.to_string()) - .collect(); +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-15-map-closure/src/main.rs:here}} ``` 或者可以将函数作为 `map` 的参数来代替闭包,像是这样: ```rust -let list_of_numbers = vec![1, 2, 3]; -let list_of_strings: Vec = list_of_numbers - .iter() - .map(ToString::to_string) - .collect(); +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-16-map-function/src/main.rs:here}} ``` 注意这里必须使用 [“高级 trait”][advanced-traits] 部分讲到的完全限定语法,因为存在多个叫做 `to_string` 的函数;这里使用了定义于 `ToString` trait 的 `to_string` 函数,标准库为所有实现了 `Display` 的类型实现了这个 trait。 @@ -63,15 +43,7 @@ let list_of_strings: Vec = list_of_numbers 另一个实用的模式暴露了元组结构体和元组结构体枚举成员的实现细节。这些项使用 `()` 作为初始化语法,这看起来就像函数调用,同时它们确实被实现为返回由参数构造的实例的函数。它们也被称为实现了闭包 trait 的函数指针,并可以采用类似如下的方式调用: ```rust -enum Status { - Value(u32), - Stop, -} - -let list_of_statuses: Vec = - (0u32..20) - .map(Status::Value) - .collect(); +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-17-map-initializer/src/main.rs:here}} ``` 这里创建了 `Status::Value` 实例,它通过 `map` 用范围的每一个 `u32` 值调用 `Status::Value` 的初始化函数。一些人倾向于函数风格,一些人喜欢闭包。这两种形式最终都会产生同样的代码,所以请使用对你来说更明白的形式吧。 @@ -83,33 +55,19 @@ let list_of_statuses: Vec = 这段代码尝试直接返回闭包,它并不能编译: ```rust,ignore,does_not_compile -fn returns_closure() -> Fn(i32) -> i32 { - |x| x + 1 -} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-18-returns-closure/src/lib.rs}} ``` 编译器给出的错误是: -```text -error[E0277]: the trait bound `std::ops::Fn(i32) -> i32 + 'static: -std::marker::Sized` is not satisfied - --> - | -1 | fn returns_closure() -> Fn(i32) -> i32 { - | ^^^^^^^^^^^^^^ `std::ops::Fn(i32) -> i32 + 'static` - does not have a constant size known at compile-time - | - = help: the trait `std::marker::Sized` is not implemented for - `std::ops::Fn(i32) -> i32 + 'static` - = note: the return type of a function must have a statically known size +```console +{{#include ../listings/ch19-advanced-features/no-listing-18-returns-closure/output.txt}} ``` 错误又一次指向了 `Sized` trait!Rust 并不知道需要多少空间来储存闭包。不过我们在上一部分见过这种情况的解决办法:可以使用 trait 对象: -```rust -fn returns_closure() -> Box i32> { - Box::new(|x| x + 1) -} +```rust,noplayground +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-19-returns-closure-trait-object/src/lib.rs}} ``` 这段代码正好可以编译。关于 trait 对象的更多内容,请回顾第十七章的 [“为使用不同类型的值而设计的 trait 对象”][using-trait-objects-that-allow-for-values-of-different-types] 部分。 diff --git a/src/ch19-06-macros.md b/src/ch19-06-macros.md index 31154d5..3619ea9 100644 --- a/src/ch19-06-macros.md +++ b/src/ch19-06-macros.md @@ -2,7 +2,7 @@ > [ch19-06-macros.md](https://github.com/rust-lang/book/blob/main/src/ch19-06-macros.md) >
-> commit 7ddc46460f09a5cd9bd2a620565bdc20b3315ea9 +> commit acc806a06b5a23c7397b7218aecec0e774619512 我们已经在本书中使用过像 `println!` 这样的宏了,不过还没完全探索什么是宏以及它是如何工作的。**宏**(*Macro*)指的是 Rust 中一系列的功能:使用 `macro_rules!` 的 **声明**(*Declarative*)宏,和三种 **过程**(*Procedural*)宏: @@ -40,19 +40,8 @@ let v: Vec = vec![1, 2, 3]; 文件名: src/lib.rs -```rust -#[macro_export] -macro_rules! vec { - ( $( $x:expr ),* ) => { - { - let mut temp_vec = Vec::new(); - $( - temp_vec.push($x); - )* - temp_vec - } - }; -} +```rust,noplayground +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-28/src/lib.rs}} ``` 示例 19-28: 一个 `vec!` 宏定义的简化版本 @@ -68,7 +57,7 @@ macro_rules! vec { 宏定义中有效模式语法和在第十八章提及的模式语法是不同的,因为宏模式所匹配的是 Rust 代码结构而不是值。回过头来检查下示例 19-28 中模式片段什么意思。对于全部的宏模式语法,请查阅[参考]。 -[参考]: https://doc.rust-lang.org/reference/macros.html +[参考]: https://doc.rust-lang.org/reference/macros-by-example.html 首先,一对括号包含了整个模式。接下来是美元符号( `$` ),后跟一对括号,捕获了符合括号内模式的值以用于替换后的代码。`$()` 内则是 `$x:expr` ,其匹配 Rust 的任意表达式,并将该表达式记作 `$x`。 @@ -79,18 +68,20 @@ macro_rules! vec { 现在让我们来看看与此单边模式相关联的代码块中的模式:对于每个(在 `=>` 前面)匹配模式中的 `$()` 的部分,生成零个或更多个(在 `=>` 后面)位于 `$()*` 内的 `temp_vec.push()` ,生成的个数取决于该模式被匹配的次数。`$x` 由每个与之相匹配的表达式所替换。当以 `vec![1, 2, 3];` 调用该宏时,替换该宏调用所生成的代码会是下面这样: ```rust,ignore -let mut temp_vec = Vec::new(); -temp_vec.push(1); -temp_vec.push(2); -temp_vec.push(3); -temp_vec +{ + let mut temp_vec = Vec::new(); + temp_vec.push(1); + temp_vec.push(2); + temp_vec.push(3); + temp_vec +} ``` 我们已经定义了一个宏,其可以接收任意数量和类型的参数,同时可以生成能够创建包含指定元素的 vector 的代码。 `macro_rules!` 中有一些奇怪的地方。在将来,会有第二种采用 `macro` 关键字的声明宏,其工作方式类似但修复了这些极端情况。在此之后,`macro_rules!` 实际上就过时(deprecated)了。在此基础之上,同时鉴于大多数 Rust 程序员 **使用** 宏而非 **编写** 宏的事实,此处不再深入探讨 `macro_rules!`。请查阅在线文档或其他资源,如 [“The Little Book of Rust Macros”][tlborm] 来更多地了解如何写宏。 -[tlborm]: https://danielkeep.github.io/tlborm/book/index.html +[tlborm]: https://veykril.github.io/tlborm/ ### 用于从属性生成代码的过程宏 @@ -114,7 +105,7 @@ pub fn some_name(input: TokenStream) -> TokenStream { 定义过程宏的函数以 TokenStream 作为输入并生成 TokenStream 作为输出。 TokenStream 类型由包含在 Rust 中的 proc_macro crate 定义并表示令牌序列。 这是宏的核心:宏所操作的源代码构成了输入 TokenStream,宏产生的代码是输出 TokenStream。 该函数还附加了一个属性,用于指定我们正在创建的程序宏类型。 我们可以在同一个 crate 中拥有多种程序宏。 -让我们看看不同种类的程序宏。 我们将从一个自定义的派生宏开始,然后解释使其他形式不同的小差异。 +让我们看看不同种类的程序宏。 我们将从一个自定义的派生宏开始,然后解释使其他形式不同的小差异。 ### 如何编写自定义 `derive` 宏 @@ -122,23 +113,15 @@ pub fn some_name(input: TokenStream) -> TokenStream { 文件名: src/main.rs -```rust,ignore -use hello_macro::HelloMacro; -use hello_macro_derive::HelloMacro; - -#[derive(HelloMacro)] -struct Pancakes; - -fn main() { - Pancakes::hello_macro(); -} +```rust,ignore,does_not_compile +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-30/src/main.rs}} ``` 示例 19-30: crate 用户所写的能够使用过程式宏的代码 运行该代码将会打印 `Hello, Macro! My name is Pancakes!` 第一步是像下面这样新建一个库 crate: -```text +```console $ cargo new hello_macro --lib ``` @@ -146,28 +129,14 @@ $ cargo new hello_macro --lib 文件名: src/lib.rs -```rust -pub trait HelloMacro { - fn hello_macro(); -} +```rust,noplayground +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/hello_macro/src/lib.rs}} ``` 现在有了一个包含函数的 trait 。此时,crate 用户可以实现该 trait 以达到其期望的功能,像这样: ```rust,ignore -use hello_macro::HelloMacro; - -struct Pancakes; - -impl HelloMacro for Pancakes { - fn hello_macro() { - println!("Hello, Macro! My name is Pancakes!"); - } -} - -fn main() { - Pancakes::hello_macro(); -} +{{#rustdoc_include ../listings/ch19-advanced-features/no-listing-20-impl-hellomacro-for-pancakes/pancakes/src/main.rs}} ``` 然而,他们需要为每一个他们想使用 `hello_macro` 的类型编写实现的代码块。我们希望为其节约这些工作。 @@ -176,7 +145,7 @@ fn main() { 下一步是定义过程式宏。在编写本部分时,过程式宏必须在其自己的 crate 内。该限制最终可能被取消。构造 crate 和其中宏的惯例如下:对于一个 `foo` 的包来说,一个自定义的派生过程宏的包被称为 `foo_derive` 。在 `hello_macro` 项目中新建名为 `hello_macro_derive` 的包。 -```text +```console $ cargo new hello_macro_derive --lib ``` @@ -187,45 +156,15 @@ $ cargo new hello_macro_derive --lib 文件名: hello_macro_derive/Cargo.toml ```toml -[lib] -proc-macro = true - -[dependencies] -syn = "1.0" -quote = "1.0" +{{#include ../listings/ch19-advanced-features/listing-19-31/hello_macro/hello_macro_derive/Cargo.toml:6:12}} ``` 为定义一个过程式宏,请将示例 19-31 中的代码放在 `hello_macro_derive` crate 的 *src/lib.rs* 文件里面。注意这段代码在我们添加 `impl_hello_macro` 函数的定义之前是无法编译的。 文件名: hello_macro_derive/src/lib.rs - - -> 在 Rust 1.31.0 时,`extern crate` 仍是必须的,请查看
-> https://github.com/rust-lang/rust/issues/54418
-> https://github.com/rust-lang/rust/pull/54658
-> https://github.com/rust-lang/rust/issues/55599 - -```rust,ignore -extern crate proc_macro; - -use crate::proc_macro::TokenStream; -use quote::quote; -use syn; - -#[proc_macro_derive(HelloMacro)] -pub fn hello_macro_derive(input: TokenStream) -> TokenStream { - // 将 Rust 代码解析为语法树以便进行操作 - let ast = syn::parse(input).unwrap(); - - // 构建 trait 实现 - impl_hello_macro(&ast) -} +```rust,ignore,does_not_compile +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-31/hello_macro/hello_macro_derive/src/lib.rs}} ``` 示例 19-31: 大多数过程式宏处理 Rust 代码时所需的代码 @@ -278,17 +217,7 @@ DeriveInput { 文件名: hello_macro_derive/src/lib.rs ```rust,ignore -fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { - let name = &ast.ident; - let gen = quote! { - impl HelloMacro for #name { - fn hello_macro() { - println!("Hello, Macro! My name is {}", stringify!(#name)); - } - } - }; - gen.into() -} +{{#rustdoc_include ../listings/ch19-advanced-features/listing-19-33/hello_macro/hello_macro_derive/src/lib.rs:here}} ``` 示例 19-33: 使用解析过的 Rust 代码实现 `HelloMacro` trait @@ -308,9 +237,7 @@ fn impl_hello_macro(ast: &syn::DeriveInput) -> TokenStream { 此时,`cargo build` 应该都能成功编译 `hello_macro` 和 `hello_macro_derive` 。我们将这些 crate 连接到示例 19-30 的代码中来看看过程宏的行为!在 *projects* 目录下用 `cargo new pancakes` 命令新建一个二进制项目。需要将 `hello_macro` 和 `hello_macro_derive` 作为依赖加到 `pancakes` 包的 *Cargo.toml* 文件中去。如果你正将 `hello_macro` 和 `hello_macro_derive` 的版本发布到 [crates.io](https://crates.io/) 上,其应为常规依赖;如果不是,则可以像下面这样将其指定为 `path` 依赖: ```toml -[dependencies] -hello_macro = { path = "../hello_macro" } -hello_macro_derive = { path = "../hello_macro/hello_macro_derive" } +{{#include ../listings/ch19-advanced-features/no-listing-21-pancakes/pancakes/Cargo.toml:7:9}} ``` 把示例 19-30 中的代码放在 *src/main.rs* ,然后执行 `cargo run`:其应该打印 `Hello, Macro! My name is Pancakes!`。其包含了该过程宏中 `HelloMacro` trait 的实现,而无需 `pancakes` crate 实现它;`#[derive(HelloMacro)]` 增加了该 trait 实现。