Updated 'src/smart_pointers/deref-t'.

This commit is contained in:
Hector PENG
2026-04-03 14:30:42 +08:00
parent 6ee46dc93a
commit faeed0ebad
3 changed files with 41 additions and 38 deletions

View File

@@ -17,9 +17,12 @@ impl<T> Deref for MyBox<T> {
}
fn main() {
let x = 5;
let y = MyBox::new(x);
hello("Rust");
assert_eq! (5, x);
assert_eq! (5, *y);
let m = MyBox::new(String::from("Rust"));
hello(&m);
}
fn hello(name: &str) {
println! ("你好,{name}");
}

View File

@@ -103,7 +103,7 @@
- [灵巧指针](Ch15_Smart_Pointers.md)
- [使用 `Box<T>` 指向内存堆上的数据](smart_pointers/box-t.md)
- [使用 `Deref` 特质将灵巧指针视为常规引用](smart_pointers/deref-t.md)
- [将灵巧指针视为普通引用](smart_pointers/deref-t.md)
- [使用 `Drop` 特质在内存清理时运行代码](smart_pointers/drop-t.md)
- [引用有计数的灵巧指针 `Rc<T>`](smart_pointers/rc-t.md)
- [`RefCell<T>` 与内部可变性模式](smart_pointers/refcell-t.md)

View File

@@ -5,7 +5,6 @@
咱们首先来看看解除引用运算符对常规引用的工作原理。然后,我们将尝试定义一个行为类似于 `Box<T>` 的自定义类型,并了解为什么解引用运算符没有像我们新定义的类型上的引用那样工作。我们将探讨实现 `Deref` 特质,是如何使灵巧指针以类似于引用的方式工作成为可能的。然后,我们将研究 Rust 的解引用强制转换特性,以及他是如何让咱们既可以使用引用,又可以使用灵巧指针的。
## 沿着引用前往值
普通引用属于一种指针,而看待指针的一种方式是,指向存储于别处的值的箭头。在下面清单 15-6 中,我们创建了一个到 `i32` 值的引用,然后使用解引用运算符来跟随该引用前往值。
@@ -155,45 +154,48 @@ impl<T> Deref for MyBox<T> {
我们以 `&self.0` 填入 `deref` 方法的主体,以便 `deref` 返回一个到我们打算以 `*` 运算符访问的值的引用;回顾第 5 章中 [以元组结构体创建不同类型](../structs/defining_and_instantiating.md#以元组结构体创建不同类型) 中,`.0` 可以访问元组结构体中的第一个值。清单 15-9 中对 `MyBox<T>` 值调用 `*``main` 函数现在会编译,且两个断言都会通过!
如果没有 `Deref` 特质,编译器只能 `&` 引用进行解引用。`deref` 方法给编译器这样的能力:取一个实现 `Deref` 的任何类型的值,并调用 `deref` 方法来获一个他知道如何解除引用的 `&` 引用
没有 `Deref` 特质,编译器只能解引用 `&` 引用。`deref` 方法给编译器实现 `Deref` 的任何类型的值,并调用 `deref` 方法来获一个他知道怎样解引用的引用的能力
当我们在清单 `15-9` 中输入 `*y` 时,在幕后 Rust 实际上运行了下面这代码:
当我们在清单 `15-9` 中输入 `*y`Rust 在幕后实际上运行了下面这代码:
```rust
*(y.deref())
```
Rust `*` 运算符替换为`deref` 方法的调用,然后普通解引用,这样们就不必考虑是否需要调用 `deref` 方法。这 Rust 特性让我们可以编写功能相同的代码,无论我们有一个常规引用还是个实现 `Deref` 的类型。
Rust `deref` 方法的调用替换 `*` 运算符,然后进行普通解引用,这样们就不必考虑是否需要调用 `deref` 方法。这 Rust 特性让我们可以编写作用一致的代码,无论我们有个普通引用还是个实现 `Deref` 的类型。
`deref` 方法返回一个值的引用,以及 `*(y.deref())` 中括号外的普通解引用仍然是必要的,其原因与所有权系统有关。如果 `deref` 方法直接返回值,而不是返回值的引用,值就会被移出 `self`。在这种情况下,或者在大多数使用解引用操作符的情况下,我们不希望取得 `MyBox<T>`的值的所有权。
`deref` 方法返回值的引用,以及 `*(y.deref())` 中括号外的普通解引用仍然是必要的原因与所有权系统有关。 `deref` 方法直接返回值,而不是值的引用时,那么值就会从 `self` 中迁出。在这种情况下,或者在大多数使用解引用运算符的情况下,我们不希望取得 `MyBox<T>` 内的内部值的所有权。
请注意,每次我们在代码中使用 `*` 时, `*` 运算符被替换`deref` 方法的调用,然后仅调用一次 `*` 运算符。因为 `*` 运算符的替换不会无限递归,所以我们最终得到类型 `i32` 的数据,他与清单 15-9 中 `assert_eq!` 中的 `5` 匹配
请注意,每次我们在代码中使用 `*` 时, `*` 运算符都会被替换`deref` 方法的调用,然后仅一次 `*` 运算符的调用。因为 `*` 运算符的替换不会无限递归,所以我们会以类型 `i32` 的数据结束,其与清单 15-9 中 `assert_eq!` 中的 `5` 一致
## 在函数方法中使用解引用强制转换
## 在函数方法中使用解引用强制转换
*解引用强制转换* 会转换实现 `Deref` 特质的类型的引用为另一类型的引用。例如,解引用强制转换可以转换 `&String``&str`,因为 `String` 实现了 `Deref`,因此他返回 `&str`。解引用强制转换是 Rust 对函数和方法的参数执行的一项便利操作,且仅对实现 `Deref` 特质的类型起作用。当我们作为参数传递特定类型的值的引用给函数或方法,而该引用与函数或方法定义中的参数类型不一致时,他会自动发生。对 `Deref` 方法的调用序列,会转换我们提供的类型为参数需要的类型。
*解引用强制转换deref coercion* 将对实现了 `Deref` 特质的某种类型的引用,转换为对另一类型的引用。例如,解引用强制转换可以将 `&String` 转换为 `&str`,因为 `String` 实现了 `Deref`,所以他会返回 `&str`。解引用强制转换是 Rust 对函数和方法的参数执行的一种便利,只对实现 `Deref` 特质的类型起作用。当我们把对某一特定类型的值的引用作为参数传递给函数或方法时,它就会自动发生,而该参数与函数或方法定义中的参数类型并不匹配。一系列对 `Deref` 方法的调用将我们提供的类型转换为参数需要的类型。
> **译注**
>
> 在面向对象编程语言 Java 中,类似的特性叫 ["自动装箱"](https://java.xfoss.com/Ch10_Numbers_and_Statics_Numbers_Matter.html#%E8%87%AA%E5%8A%A8%E8%A3%85%E7%AE%B1%E6%A8%A1%E7%B3%8A%E5%8E%9F%E7%94%9F%E5%80%BC%E4%B8%8E%E5%AF%B9%E8%B1%A1%E4%B9%8B%E9%97%B4%E7%9A%84%E7%95%8C%E7%BA%BF)。
解引用强制转换特性之所以添加到 Rust 中,是为编写函数和方法调用的程序员,无需以 `&``*` 添加过多的显式引用和解引用。解引用强制转换特性,还让我们可以写出更多既可以用于引用,也可以用于灵巧指针的代码。
> 注:在面向对象编程语言 Java 中,类似的特性叫 ["自动装箱"](https://java.xfoss.com/Ch10_Numbers_and_Statics_Numbers_Matter.html#自动装箱模糊原生值与对象之间的界线)
解引用强制转换被添加到 Rust 中,这样编写函数和方法调用的程序员就不需要用 `&``*` 添加那么多显式引用和解引用。解引用强制转换特性,也让咱们写出更多既可以用于引用,也可以用于灵巧指针的代码。
为了看到解引用强制转换的作用,下面咱们来使用清单 15-8 中定义的 `MyBox<T>` 类型,以及清单 15-10 中添加的 `Deref` 的实现。清单 15-11 给出了一个有字符串切片参数的函数定义:
为了看到解引用强制转换的实际效果,我们来使用我们在清单 15-8 中定义的 `MyBox<T>` 类型,以及我们在清单 15-10 中添加的 `Deref` 的实现。下面清单 15-11 展示了一个函数定义,其有着一个字符串切片的参数
<a name="listing_15-11"></a>
文件名:`src/main.rs`
```rust
fn hello(name: &str) {
println! ("你好,{name}");
println! ("你好,{name}");
}
```
*清单 15-11:参数 `name` 为 `&str` 类型的 `hello` 函数*
**清单 15-11**`hello` 函数,有着 `&str` 类型的参数 `name`
我们可以一个字符串切片作为参数调用 `hello` 函数,例如 `hello("Rust");`。解引用强制转换使我们可以用`MyBox<String>` 类型值的引用调用 `hello`,如清单 15-12 所示:
我们可以一个字符串切片作为参数调用 `hello` 函数,例如 `hello("Rust");`。解引用强制转换使得以`MyBox<String>` 类型值的引用调用 `hello` 可行,如清单 15-12 所示:
<a name="listing_15-12"></a>
文件名:`src/main.rs`
```rust
@@ -205,12 +207,13 @@ fn main() {
}
```
*清单 15-12:使用对 `MyBox<String>` 值的引用调用 `hello`,由于解引用强制转换,其可以工作*
**清单 15-12**:以`MyBox<String>` 值的引用调用 `hello`,由于解引用强制转换这可以正常工作
这里我们参数 `&m` 调用 `hello` 函数,他是`MyBox<String>` 值的一个引用。因为我们在清单 15-10 中对 `MyBox<T>` 实现了 `Deref`Rust 可以通过调用 `deref` `&MyBox<String>` 变成 `&String`。标准库提供了一个 `String` 上的 `Deref` 的实现,其返回一个字符串片,这 `Deref``API` 文档中。Rust 再次调用 `deref`,将 `&String` 变成 `&str`,这与 `hello` 函数的定义相符
这里我们参数 `&m` 调用 `hello` 函数,这是个`MyBox<String>` 值的引用。因为我们在清单 15-10 中对 `MyBox<T>` 实现了 `Deref`Rust 可以通过调用 `deref` 转换 `&MyBox<String>` `&String`。标准库提供了 `String` 上的 `Deref` 的实现,其返回一个字符串片,这位于 `Deref` 的 API 文档中。Rust 再次调用 `deref` 来转换 `&String` `&str`,这与 `hello` 函数的定义一致
如果 Rust 没有实现解引用强制转换,为了用一个 `&MyBox<String>` 类型的值调用 `hello`,我们就必须清单 15-13 中的代码,而不是清单 15-12 中的代码。
Rust 没有实现解引用强制转换,为了 `&MyBox<String>` 类型的值调用 `hello`,我们就必须编写下面清单 15-13 中的代码,而不是清单 15-12 中的代码。
<a name="listing_15-13"></a>
文件名:`src/main.rs`
```rust
@@ -220,29 +223,26 @@ fn main() {
}
```
*清单 15-13:如果 Rust 没有解引用强制转换,我们将不得不编写的代码*
**清单 15-13**:若 Rust 没有解引用强制转换,我们将必须编写的代码
`(*m)` `MyBox<String>` 解引用`String`。然后 `&``[..]` `String` 等于整个字符串的一个字符串切片,以匹配 `hello` 的签名。由于涉及所有这些符号,这段没有解引用强制转换的代码更难阅读、编写和理解。 解引用强制转换允许 Rust 自动为我们处理这些转换。
其中的 `(*m)` 解引用 `MyBox<String>``String`。然后`&``[..]`与整个字符串相等的该 `String` 字符串切片,以 `hello` 的签名一致。这段不带解引用强制转换的代码,在涉及所有这些符号下,更难阅读、编写和理解。解引用强制转换允许 Rust 自动为我们处理这些转换。
所涉及的类型定义了 `Deref` 特质时Rust 将分析这些类型,并根据需要多次使用 `Deref::deref`,来获得与参数类型匹配的引用。需插入 `Deref::deref` 次数,是在编译时就已确定,所以利用解引用强制转换的优势,没有运行时的代价
所涉及的类型定义了 `Deref` 特质时Rust 将分析这些类型,并根据需要多次使用 `Deref::deref`,来获得与参数类型一致的引用。需插入 `Deref::deref` 次数在编译时确定,因此利用解引用强制转换没有运行时损失
## 解引用强制转换与可变性的互动方式
## 处理可变引用下的解引用强制转换
**How Deref Coercion Interacts with Mutability**
与咱们使用 `Deref` 特质重写不可变引用上的 `*` 运算符类似,咱们可以使用 `DerefMut` 特质重写可变引用上的 `*` 运算符。
Rust 会在发现以下三种情形下的类型与特质实现时,执行解引用强制转换:
与使用 `Deref` 特质重写不可变引用上的 `*` 运算符类似,咱们可以使用 `DerefMut` 特质,重写可变引用上的 `*` 运算符。
1.`T: Deref<Target=U>` 时,会从 `&T` 强制转换为 `&U`;
2.`T: DerefMut<Target=U>` 时,会从 `&mut T` 强制转换为 `&mut U`;
3.`T: Deref<Target=U>` 时,会从 `&mut T` 强制转换为 `&U`
在以下三种情形下找到类型与特质实现时Rust 会执行解引用强制转换
前两种情况相同,只是第二种情况实现了可变性。第一种情况表明,当咱们有个 `&T`,并且 `T` 对某种类型 `U` 实现了 `Deref` 时,咱们可以透明地获得 `&U`。第二种情况表明,对于可变引用,也会发生相同的解引用强制转换
-`T: Deref<Target=U>` 时,会从 `&T` 转换为 `&U`;
-`T: DerefMut<Target=U>` 时,会从 `&mut T` 转换为 `&mut U`;
-`T: Deref<Target=U>` 时,会从 `&mut T` 转换为 `&U`
前两种情况彼此相同,只是第二种情况实现了可变性。第一种情况表明,如果咱们有一个 `&T`,并且 `T` 实现了对某种类型 `U``Deref`,咱们可以透明地得到一个 `&U`。第二种情况表明相同的解引用强制转换发生在可变引用上。
第三种情况比较棘手Rust 还会将可变引用强制转换为不可变引用。但反过来是 ** 可行的不可变引用永远不会强制转换为可变引用。由于借用规则如果咱们有一个可变引用则该可变引用必须是对那个数据的唯一引用否则程序将无法编译。将一个可变引用转换为一个不可变引用永远不会违反借用规则。将不可变引用转换为可变引用则要求那个初始不可变引用是对那个数据的唯一不可变引用但借用规则并不能保证这一点。因此Rust 不能假设将不可变引用转换为可变引用是可行的。
第三种情况比较棘手Rust 还会强制转换可变引用为不可变引用。但反过来是 ** 可行的不可变引用永远不会强制转换为可变引用。由于借用规则当咱们有个可变引用时该可变引用必须是对该数据的唯一引用否则程序将不编译。转换一个可变引用为一个不可变引用永远不会违反借用规则。而转换不可变引用为可变引用将要求初始不可变引用是对该数据的唯一不可变引用但借用规则不能保证这一点。因此Rust 无法做出转换不可变引用为可变引用是可行的假设。
End