From 6ee46dc93aeb57755af4440031cb11faca3d7989 Mon Sep 17 00:00:00 2001 From: Hector PENG Date: Fri, 3 Apr 2026 12:50:29 +0800 Subject: [PATCH] Updated 'src/smart_pointers/deref-t'. --- "\\" | 204 ------------------ projects/deref-example/Cargo.toml | 6 + projects/deref-example/src/main.rs | 25 +++ .../traits.md | 2 +- src/smart_pointers/deref-t.md | 98 ++++----- 5 files changed, 70 insertions(+), 265 deletions(-) delete mode 100644 "\\" create mode 100644 projects/deref-example/Cargo.toml create mode 100644 projects/deref-example/src/main.rs diff --git "a/\\" "b/\\" deleted file mode 100644 index c39f1fb..0000000 --- "a/\\" +++ /dev/null @@ -1,204 +0,0 @@ -# 使用 `Box` 指向堆上的数据 - -最简单的灵巧指针是 *匣子* ,其类型写为 `Box`。匣子允许咱们存储数据于堆上,而非栈上。留在栈上的指向堆数据的指针。请参考 [第 4 章](../ownership/about_ownership.md#diff_between_stack_n_heap) 回顾栈和堆之间的区别。 - -除了在堆上而不是栈上存储他们的数据外,匣子数据结构并无性能开销。但他们也没有太多额外的能力。咱们将在以下这些情况下经常使用他们: - -- 当咱们有个在编译时无法得知其大小的类型,而打算在需要精确大小的上下文中,使用这种类型的值时; -- 当咱们有着大量数据,而打算转移所有权,但要确保转移时数据不会被拷贝时; -- 当咱们打算拥有某个值,且只关心他是否实现特定特质,而非属于某种特定类型时。 - -我们将在 [“以匣子数据结构实现递归类型”](#以匣子数据结构实现递归类型) 中演示第一种情况。在第二种情形下,转移大量数据的所有权会耗时很长,因为数据会在栈上来回拷贝。为了提升这种情况下的性能,我们可以存储大量数据于堆上的一个匣子中。然后,只有少量的指针数据在栈上来回拷贝,而他引用的数据保留在堆上的一处。第三种情况称为 *特质对象*,第 18 中的 [使用特质对象抽象共用行为](../oop/trait_objects.md) 专门讨论这一主题。因此,咱们在这里学到的内容将在那个小节中再次应用! - - -## 在堆上存储数据 - -在讨论 `Box` 的堆存储用例前,我们将介绍这一语法,以及怎样与 `Box` 内存储的值交互。 - -下面清单 15-1 展示了怎样使用匣子在堆上存储 `i32` 的值: - - -文件名:`src/main.rs` - -```rust -fn main() { - let b = Box::new(5); - println! ("b = {b}"); -} -``` - -**清单 15-1**:使用匣子在堆上存储 `i32` 值 - -我们定义变量 `b` 为有着指向值 `5` 的一个 `Box` 值,而值 `5` 分配在堆上。这个程序将打印 `b = 5`;在这种情况下,我们可以访问匣子中的数据,就像我们访问栈上的这一数据一样。与任何自有值一样,当匣子超出作用域时,正如 `b` 在 `main` 结束处所做的那样,他将被解除内存分配(释放)。解除内存分配会针对匣子(存储在栈上)和他指向的数据(存储在堆上)同时发生。 - -在堆上放置单个值并不是很有用,因此咱们不会经常以这种方式单独使用匣子。在大多数情况下,让 i32 这样的值处于他们默认存储所在的栈上更为合适。我们来看看一种情形,其中匣子允许我们定义一些,在没有匣子数据结构时我们不被允许定义的类型。 - - -## 通过匣子数据结构得到递归类型 - -*递归类型* 的值,可以将同一类型的另一个值作为自身的一部分。递归类型会带来一个问题,因为 Rust 需要在编译时知道类型占用了多少空间。然而,递归类型的值的嵌套理论上可以无限地延续,因此 Rust 无法知道该值需要多少空间。由于匣子有着已知大小,我们可以通过在递归类型定义中插入匣子来得到递归类型。 - -作为递归类型的示例,我们来探讨一下 *构造列表,cons list*(the *cons* tructs *list*)。这是一种常见于函数式编程语言中的数据类型。除了递归外,我们将定义的构造列表类型很简单;因此,当咱们遇到涉及递归类型的更复杂的情况时,我们将使用的示例中的概念将很有用。 - - -### 理解构造列表 - -所谓 *构造列表*,是 Lisp 编程语言及其方言中的数据结构,由嵌套对构成,是 Lisp 版本的链表。他的名称来自 [Lisp 中的 `cons` 函数](https://zh.wikipedia.org/zh-cn/%E5%88%97%E8%A1%A8%E6%A7%8B%E9%80%A0%E5%87%BD%E6%95%B8)(*construct function* 的缩写),该函数会根据他的两个参数构造一个新的对。通过对由一个值和另一个对组成的对调用 `cons`,我们可以构造由递归对组成的构造列表。 - -例如,下面是个构造列表的伪代码表示,包含列表 `1, 2, 3`,其中每个位于括号中: - -```lisp -(1, (2, (3, Nil))) -``` - -构造列表中的每个项目都包含两个元素:当前项目的值和下一个项目的值。列表中的最后一个项目仅包含一个名为 `Nil` 的值,且没有下一项目。构造列表是通过递归调用 `cons` 函数产生的。表示递归基础情形的规范名称是 `Nil`。请注意,这与第 6 章中讨论的 “null” 或 “nil” 概念不同,后者属于无效或不存在的值。 - -构造列表不是 Rust 中的常用数据结构。大多数时候,当咱们在 Rust 中有个项目列表时,使用 `Vec` 是种更好的选择。在其他时间,更复杂的递归数据类型在各种情况下 *都* 很有用,但从这一章中的构造列表开始,我们可以探讨匣子数据结构,怎样让我们不受干扰地定义递归数据类型。 - -下面清单 15-2 包含用于构造列表的枚举定义。请注意,这段代码还不会编译,因为其中的 `List` 类型没有已知的大小,我们将演示这点。 - - -文件名:`src/main.rs` - -```rust -enum List { - cons(i32, List), - Nil, -} -``` - -**清单 15-2**:首次尝试定义一个枚举,来表示 `i32` 值的构造列表数据结构 - -> **注意**:我们正在出于这个示例的目的,实现一个仅包含 `i32` 值的构造列表。正如我们在第 10 章中讨论的那样,我们本可以使用泛型来实现,定义一种可以存储任何类型值的构造列表。 - -使用这个 `List` 类型来存储列表 `1, 2, 3`,将看起来像下面清单 15-3 中的代码: - - -文件名:`src/main.rs` - -```rust -use crate::List::{Cons, Nil}; - -fn main() { - let list = Cons(1, Cons(2, Cons(3, Nil))); -} -``` - -**清单 15-3**:使用 `List` 枚举来存储列表 `1, 2, 3` - -其中第一个 `Cons` 包含 `1` 和另一个 `List` 值。这个 `List` 值是另一个 `Cons` 值,其中包含 `2` 与另一个 `List`。这个 `List` 值又是另一个 `Cons` 值,其中包含 `3` 和一个 `List` 值,该值最终为 `Nil`,表示列表结束的非地归变种。 - -当我们尝试编译清单 15-3 中的代码时,我们会得到下面清单 15-4 中所示的报错: - - -```console -$ cargo run - Compiling cons-list v0.1.0 (/home/hector/rust-lang-zh_CN/projects/cons-list) -error[E0072]: recursive type `List` has infinite size - --> src/main.rs:1:1 - | -1 | enum List { - | ^^^^^^^^^ -2 | Cons(i32, List), - | ---- recursive without indirection - | -help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle - | -2 | Cons(i32, Box), - | ++++ + - -error[E0391]: cycle detected when computing when `List` needs drop - --> src/main.rs:1:1 - | -1 | enum List { - | ^^^^^^^^^ - | - = note: ...which immediately requires computing when `List` needs drop again - = note: cycle used when computing whether `List` needs drop - = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information - -Some errors have detailed explanations: E0072, E0391. -For more information about an error, try `rustc --explain E0072`. -error: could not compile `cons-list` (bin "cons-list") due to 2 previous errors -``` - -**清单 15-4**:尝试定义递归枚举时,我们得到的报错 - -报错显示这种类型“有着无限大小,has infinite size”。原因是咱们以递归的变种定义了 `List`:他直接包含本身的另一个值。因此,Rust 无法计算出存储 `List` 值需要多少空间。我们来分析一下为什么我们会得到这个报错。首先,我们将看看,Rust 如何决定存储非递归类型需要多少空间。 - - -### 计算非递归类型的大小 - -回顾咱们在第 6 章中讨论枚举定义时,在 [清单 6-2](../enums_and_pattern_matching/defining_an_enum.md#listing_6-2) 中定义的 `Message` 枚举: - -```rust -enum Message { - Quit, - Move { x: i32, y: i32 }, - Write(String), - ChangeColor(i32, i32, i32), -} -``` - -为了确定为 `Message` 值分配多少空间,Rust 会遍历每个变种,以发现哪个变种需要最多的空间。Rust 发现 `Message::Quit` 不需要任何空间,`Message::Move` 需要足够的空间来存储两个 `i32` 值,以此类推。由于只有一个变种会被用到,因此 `Message` 值所需的最大内存空间,便是存储最大变种将占用的空间。 - -与 Rust 尝试确定像是清单 15-2 中的 `List` 枚举,这样的递归类型需要多少空间时的情况对比这一过程。编译器首先查看 `Cons` 变种,他包含一个 `i32` 类型的值和一个 `List` 类型的值。因此,`Cons` 需要的空间量等于 `i32` 的大小加上 `List` 的大小。为了计算出 `List` 类型需要多少内存,编译器会从 `Cons` 变种开始查看变种。 `Cons` 变种包含一个 `i32` 类型的值和一个 `List` 类型的值,而这个过程会无限地继续下去,如图 15-1 中所示。 - - -![由无限个 `Cons` 变种构成的无限 `List`](../images/15-01.svg) - -**图 15-1**:由无限个 `Cons` 变种构成的无限 `List` - - -### 使用 `Box` 获得已知大小的递归类型 - - -因为 Rust 无法计算出要为以递归方式定义出的类型分配多少空间,所以编译器会给出带有下面这个有用的建议的报错: - -```console -help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable - | -2 | Cons(i32, Box), - | ++++ + -``` - -在此建议中,“间接,indirection” 意味着我们不应直接存储一个值,而应该改变数据结构,通过存储一个指向该值的指针,间接存储该值。 - -由于 `Box` 是个指针,Rust 总是知道 `Box` 需要多少内存空间:指针的大小不会根据他指向的数据量而变化。这意味着咱们可以在 `Cons` 变种里放入一个 `Box`,而不是直接放入另一个 `List` 值。`Box` 将指向下一个 `List` 值,他将在内存堆上而不是在 `Cons` 变种内。从概念上讲,咱们仍然有一个列表,用持有其他列表的列表来创建,但现在这种实现更像是把列表项目放在彼此的旁边,而不是放在彼此的里面。 - -咱们可以把清单 15-2 中 `List` 枚举的定义和清单 15-3 中 `List` 的用法,改为下面清单 15-5 中的代码,这样就可以编译了: - -文件名:`src/main.rs` - -```rust -#[derive(Debug)] -enum List { - Cons(i32, Box), - Nil, -} - -use crate::List::{Cons, Nil}; - -fn main() { - let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); - - println! ("list: {:?}", list); -} -``` - -*清单 15-5:使用 `Box` 的 `List` 的定义,以便有已知的大小* - -`Cons` 变种需要一个 `i32` 的大小,加上存储匣子指针数据的内存空间。`Nil` 变种不存储存储任何值,所以他需要的空间比 `Cons` 变种少。咱们现在知道,任何 `List` 值都会占用一个 `i32` 的大小,加上一个匣子的指针数据的大小。通过使用匣子,咱们已经破解了无限的递归链,因此编译器可以计算出存储 `List` 值所需的内存大小。下图 15-2 显示了 `Cons` 变种现在的样子: - -![由于 `Cons` 保存了一个 `Box` 而不在是无限大小的 `List`](../images/15-02.svg) - -*图 15-02:不在是无限大小的 `List`,因为 `Cons` 持有着一个 `Box`* - -匣子仅提供这种间接与内存堆的内存分配;他们不具备任何像咱们在其他灵巧指针类型中,将看到的其他特别能力。他们也没有这些特殊能力所带来的性能开销,所以在像构造列表这样的情况下,他们就能很有用,因为间接性是我们唯一需要的功能。在第 17 章,咱们还会看一下匣子的更多用例。 - -`Box` 类型是一个灵巧指针,因为他实现了 `Deref` 特质,他允许 `Box` 值被当作引用。当 `Box` 值超出作用域时,由于 `Drop` 特质的实现,匣子所指向的内存堆数据也会被清理。这两个特质对于咱们在本章后面将讨论的其他灵巧指针所提供的功能,将更加重要。咱们来更深入地探讨这两个特质。 - - -(End) - - diff --git a/projects/deref-example/Cargo.toml b/projects/deref-example/Cargo.toml new file mode 100644 index 0000000..bfefa23 --- /dev/null +++ b/projects/deref-example/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "deref-example" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/projects/deref-example/src/main.rs b/projects/deref-example/src/main.rs new file mode 100644 index 0000000..aa5dbc6 --- /dev/null +++ b/projects/deref-example/src/main.rs @@ -0,0 +1,25 @@ +use std::ops::Deref; + +struct MyBox (T); + +impl MyBox { + fn new(x: T) -> MyBox { + MyBox(x) + } +} + +impl Deref for MyBox { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +fn main() { + let x = 5; + let y = MyBox::new(x); + + assert_eq! (5, x); + assert_eq! (5, *y); +} diff --git a/src/generic_types_traits_and_lifetimes/traits.md b/src/generic_types_traits_and_lifetimes/traits.md index 7672a6e..8c96ffd 100644 --- a/src/generic_types_traits_and_lifetimes/traits.md +++ b/src/generic_types_traits_and_lifetimes/traits.md @@ -33,7 +33,7 @@ pub trait Summary { 特质在其主体可以有多个方法:方法签名一行一个地列出,每行都以分号结束。 -## 在类型上实现特质 +## 对类型实现特质 现在咱们已经定义了 `Summary` 特质方法的所需签名,咱们可以在咱们的媒体聚合器中的类型上实现他了。下面清单 10-13 显示了 `NewsArticle` 结构体上 `Summary` 特质的实现,使用标题、作者及地点字段来创建 `summaryize` 的返回值。对于 `SocialPost` 结构体,咱们定义 `summarize` 为用户名后跟帖子全文,假设帖子内容已限制为 280 字符。 diff --git a/src/smart_pointers/deref-t.md b/src/smart_pointers/deref-t.md index 00cdcdc..76ee70b 100644 --- a/src/smart_pointers/deref-t.md +++ b/src/smart_pointers/deref-t.md @@ -1,19 +1,16 @@ -# 使用 `Deref` 特质将灵巧指针视为常规引用 +# 将灵巧指针视为普通引用 -**Treating Smart Pointers Like Regular References with `Deref` Trait** +实现 `Deref` 特质允许咱们定制 *解引用运算符* `*`(请不要与乘法或通配符混淆)的行为。通过以这种方式实现 `Deref`,灵巧指针可被视为普通引用,咱们可以编写对引用进行操作的代码,并也可以对灵巧指针使用该代码。 + +咱们首先来看看解除引用运算符对常规引用的工作原理。然后,我们将尝试定义一个行为类似于 `Box` 的自定义类型,并了解为什么解引用运算符没有像我们新定义的类型上的引用那样工作。我们将探讨实现 `Deref` 特质,是如何使灵巧指针以类似于引用的方式工作成为可能的。然后,我们将研究 Rust 的解引用强制转换特性,以及他是如何让咱们既可以使用引用,又可以使用灵巧指针的。 -实现 `Deref` 特质允许咱们自定义 *解引用操作符, the dereference operator*️'*' (不要与乘法或 glob 运算符相混淆)的行为。通过实现 `Deref`,灵巧指针可以被当作普通的引用来对待,咱们便可编写对引用进行操作的代码,并将该代码也用于灵巧指针。 -咱们首先来看看解除引用操作符是如何在常规引用中工作的。然后咱们将尝试定义一个行为类似于 `Box` 的自定义类型,并看看为什么解除引用操作符在咱们新定义的类型上不像引用那样工作。咱们将探讨实现 `Deref` 特性如何使灵巧指针的工作方式与引用相似。然后咱们将看看 Rust 的 *解引用强制转换,deref coercion* 特性,以及其如何让咱们使用引用或灵巧指针工作的。 +## 沿着引用前往值 -> 注意:咱们将要建立的 `MyBox` 类型和真正的 `Box` 之间有一个很大的区别:咱们的版本不会将其数据存储在堆中。咱们把这个例子的重点放在 `Deref` 上,所以数据实际存储在哪里并不重要,重要的是类似指针的行为。 - - -## 根据引用得到值 - -常规引用是一种指针,而看待指针的一种方式,便是指向存储于别处值的一个箭头。在下面清单 15-6 种,咱们创建了一个对 `i32` 值的引用,然后使用解引用操作符,来跟随对该值的引用: +普通引用属于一种指针,而看待指针的一种方式是,指向存储于别处的值的箭头。在下面清单 15-6 中,我们创建了一个到 `i32` 值的引用,然后使用解引用运算符来跟随该引用前往值。 + 文件名:`src/main.rs` ```rust @@ -26,15 +23,15 @@ fn main() { } ``` -*清单 15-6:使用解引用操作符来跟随一个 `i32` 值的引用* +**清单 15-6**:使用解引用运算符来跟随引用前往 `i32` 值 -变量 `x` 保存着一个 `i32` 值 `5`。咱们将 `y` 设置为等于到 `x` 的引用。咱们可以断言 `x` 等于 `5`。然而,如果咱们想对 `y` 中的值进行断言,咱们必须使用 `*y` 来跟随其所指向的值的音乐(因此是 *解引用,dereference*),这样编译器才能比较具体值。一旦咱们解引用了 `y`,咱们就可以访问咱们可将其与 `5` 比较的 `y` 指向的整数值。 +变量 `x` 保存着 `i32` 值 `5`。我们设置 `y` 等于到 `x` 的引用。我们可以断言 `x` 等于 `5`。但是,当我们打算对 `y` 中的值进行断言时,我们必须使用 `*y` 来跟随这个引用前往他指向的值(因此,叫做 *解引用*),以便编译器可以比较实际值。一旦解引用了 `y`,我们就有了我们可与 `5` 比较的, `y` 指向的整数值的访问权限。 -相反,如果咱们尝试编写 `assert_eq! (5, y);`,咱们便会得到下面这样的编译报错: +相反,若我们尝试写下 `assert_eq! (5, y);`,我们就会得到下面这个编译报错: ```console -$ cargo run  ✔   - Compiling sp_demos v0.1.0 (/home/peng/rust-lang/sp_demos) +$ cargo run + Compiling deref-example v0.1.0 (/home/hector/rust-lang-zh_CN/projects/deref-example) error[E0277]: can't compare `{integer}` with `&{integer}` --> src/main.rs:6:5 | @@ -42,32 +39,20 @@ error[E0277]: can't compare `{integer}` with `&{integer}` | ^^^^^^^^^^^^^^^^^ no implementation for `{integer} == &{integer}` | = help: the trait `PartialEq<&{integer}>` is not implemented for `{integer}` - = help: the following other types implement trait `PartialEq`: - f32 - f64 - i128 - i16 - i32 - i64 - i8 - isize - and 6 others = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) For more information about this error, try `rustc --explain E0277`. -error: could not compile `sp_demos` due to previous error +error: could not compile `deref-example` (bin "deref-example") due to 1 previous error ``` -比较数字与对数字的引用是不允许的,因为他们属于不同的类型。咱们必须使用解引用操作符来跟随引用到他所指向的值。 +比较数字与对数字的引用是不允许的,因为他们属于不同的类型。我们必须使用解引用运算符来跟随引用前往其所指向的值。 ## 像引用一样使用 `Box` -**Using `Box` Like a Reference** - - -咱们可将清单 15-6 中的代码,重写为使用 `Box` 而不是引用;下面清单 15-7 中 `Box` 上使用的解引用操作符,与清单 15-6 中引用上使用的解引用操作符功能相同: +我们可以重写清单 15-6 中的代码为使用 `Box` 而不是引用;下面清单 15-7 中对 `Box` 使用的解引用运算符,与清单 15-6 中对引用使用的作用方式相同: + 文件名:`src/main.rs` ```rust @@ -80,18 +65,20 @@ fn main() { } ``` -*清单 15-7:在 `Box` 上使用解引用操作符* +**清单 15-7**:对 `Box` 使用解引用运算符 -清单 15-7 和清单 15-6 之间的主要区别在于,这里我们将 `y` 设置为指向 `x` 的拷贝值的 `Box` 实例,而不是指向 `x` 值的引用。在最后的断言中,我们可以使用解除引用操作符来跟随 `Box` 的指针,就像我们在 `y` 是一个引用时一样。接下来,我们将探讨 `Box` 有什么特别之处,使我们能够通过定义我们自己的类型来使用解引用操作符。 +清单 15-7 和清单 15-6 之间的主要区别在于,这里我们设置 `y` 为指向 `x` 的拷贝值的匣子类型的实例,而不是指向 `x` 值的引用。在最后的断言中,我们可以像 `y` 仍然是个引用那样, 使用解除引用运算符来跟随匣子的指针。接下来,我们将通过定义我们自己的匣子类型,探讨 `Box` 有什么特别之处,使我们能够使用解引用运算符。 -## 定义咱们自己的灵巧指针 +## 定义我们自己的灵巧指针 +我们来构建一个类似于标准库提供的 `Box` 类型的灵巧指针,以了解默认情况下灵巧指针类型与引用的行为方式有何不同。然后,我们将探讨怎样添加使用解除引用运算符的能力。 -咱们来建立一个类似于标准库提供的 `Box` 类型的灵巧指针,以体验灵巧指针的行为与默认的引用有什么不同。然后咱们将看看如何增加使用解除引用操作符的能力。 +> **注意**:咱们即将构建的 `MyBox` 类型与真正的 `Box` 之间有个很大的区别:我们的版本不会存储数据在堆上。我们把这个示例的重点放在 `Deref` 上,因此相比类似指针的行为,数据实际存储在何处并不重要。 -`Box` 最终被定义为了具有一个元素的元组结构体,a tuple struct,因此清单 15-8 以同样方式,定义了一个 `MyBox` 类型。咱们还将定义一个 `new` 函数,来匹配在 `Box` 上定义的 `new` 函数。 +`Box` 最终最终被定义为带有一个元素的元组结构体,因此清单 15-8 以同样方式定义了 `MyBox` 类型。我们还将定义一个 `new` 函数,以与定义在 `Box` 的 `new` 函数保持一致。 + 文件名:`src/main.rs` ```rust @@ -104,12 +91,13 @@ impl MyBox { } ``` -*清单 15-8:定义 `MyBox` 类型* +**清单 15-8**:定义 `MyBox` 类型 -我们定义了一个名为 `MyBox` 的结构,并声明了一个通用参数 `T`,因为我们希望我们的类型可以容纳任何类型的值。`MyBox` 类型是一个元组结构,其中一个元素为 `T` 类型。`MyBox::new` 函数接收一个 `T` 类型的参数,并返回一个 `MyBox` 实例,该实例保存着传入的值。 +我们定义了个名为 `MyBox` 的结构体,并声明了个泛型参数 `T`,因为我们希望我们的类型可以容纳任何类型的值。`MyBox` 类型是个元组结构体,带有一个类型为 `T` 的元素。`MyBox::new` 函数取一个类型 `T` 的参数,并返回一个包含传入值的 `MyBox` 的实例。 -我们来试着将清单 15-7 中的 `main` 函数添加到清单 15-8 中,并将其改为使用我们定义的 `MyBox` 类型而不是 `Box`。清单 15-9 中的代码不会被编译,因为 Rust 不知道如何解除对 `MyBox` 的引用。 +我们来试着添加清单 15-7 中的 `main` 函数到清单 15-8 中,并修改他为使用我们定义的 `MyBox` 类型,而不是 `Box`。清单 15-9 中的代码不会编译,因为 Rust 不知道怎样解引用 `MyBox`。 + 文件名:`src/main.rs` ```rust @@ -124,44 +112,34 @@ fn main() { *清单 15-9:试图以咱们使用引用和 `Box` 的方式使用 `MyBox`* -下面就是产生的编译报错: +下面是产生的编译报错: ```console $ cargo run - Compiling sp_demos v0.1.0 (/home/peng/rust-lang/sp_demos) + Compiling deref-example v0.1.0 (/home/hector/rust-lang-zh_CN/projects/deref-example) error[E0614]: type `MyBox<{integer}>` cannot be dereferenced --> src/main.rs:14:20 | 14 | assert_eq! (5, *y); - | ^^ + | ^^ can't be dereferenced For more information about this error, try `rustc --explain E0614`. -error: could not compile `sp_demos` due to previous error +error: could not compile `deref-example` (bin "deref-example") due to 1 previous error ``` -由于咱们未曾在这个 `MyBox` 类型上实现过其被解引用的能力,因此他无法被解引用。为实现使用 `*` 运算符的解引用,就要实现 `Deref` 特质。 +我们的 `MyBox` 类型无法被解引用,因为我们尚未对我们的类型实现这一能力。为了使 `*` 运算符下的解引用可行,我们就要实现 `Deref` 特质。 -## 通过实现 Deref 特质将类型视为引用 +## 实现 Deref 特质 -**Treating a Type Like a Reference by Implementing the `Deref` Trait** - - -正如第 10 章 ["在类型上实现特质"](Ch10_Generic_Types_Traits_and_Lifetimes.md#在类型上实现某个特质) 小节中所讨论的,要实现某个特质,咱们需要为该特质的必要方法提供实现。由标准库提供的 `Deref` 特质,要求咱们实现一个名为 `deref` 的方法,该方法借用 `self` 并返回对内部数据的引用。下面清单 15-10 包含 `Deref` 的一个实现,来添加到 `MyBox` 的定义中: +正如在第 10 章 中 [对类型实现特质](../generic_types_traits_and_lifetimes/traits.md#对类型实现特质) 小节中讨论的,要实现某个特质,我们需要提供该特质的必需方法的实现。标准库提供的 `Deref` 特质要求我们实现一个名为 `deref` 的方法,该方法会借用 `self` 并返回对内部数据的引用。下面清单 15-10 包含要添加到 `MyBox` 的定义的 `Deref` 的实现。 + 文件名:`src/main.rs` ```rust use std::ops::Deref; -struct MyBox(T); - -impl MyBox { - fn new(x: T) -> MyBox { - MyBox(x) - } -} - impl Deref for MyBox { type Target = T; @@ -171,11 +149,11 @@ impl Deref for MyBox { } ``` -*清单 15-10:在 `MyBox` 上实现 `Deref`* +**清单 15-10**:对 `MyBox` 实现 `Deref` -`type Target = T;` 语法定义了一个关联类型,an associated type,供 `Deref` 特质使用。关联类型是声明泛型参数的一种些许不同的方式,但现在咱们无需担心他们;咱们将在第 19 章中更详细地介绍他们。 +`type Target = T;` 语法定义了个供 `Deref` 特质使用的关联类型。关联类型属于声明泛型参数的一种略有不同的方式,但现在咱们无需担心他们;我们将在第 20 章中更详细地介绍他们。 -我们在 `deref` 方法的主体中填入 `&self.0`,这样 `deref` 就会返回一个我们想用 `*` 操作符访问的值的引用;回顾一下第五章 [“使用没有命名字段的元组结构体来创建不同的类型”](Ch05_Using_Structs_to_Structure_Related_Data.md#使用没有命名字段的元组结构体来创建不同的类型) 小节,`.0` 可以访问一个元组结构体中的第一个值。清单 15-9 中调用 `MyBox` 值的 `main` 函数现在可以编译了,而且断言也通过了! +我们以 `&self.0` 填入 `deref` 方法的主体,以便 `deref` 返回一个到我们打算以 `*` 运算符访问的值的引用;回顾第 5 章中 [以元组结构体创建不同类型](../structs/defining_and_instantiating.md#以元组结构体创建不同类型) 中,`.0` 可以访问元组结构体中的第一个值。清单 15-9 中对 `MyBox` 值调用 `*` 的 `main` 函数现在会编译,且两个断言都会通过! 如果没有 `Deref` 特质,编译器只能对 `&` 引用进行解引用。`deref` 方法给了编译器这样的能力:取一个实现 `Deref` 的任何类型的值,并调用 `deref` 方法来获得一个他知道如何解除引用的 `&` 引用。