update Deref Coercion, close #508

pull/520/head
KaiserY 3 years ago
parent 81618b8a7d
commit a737fe8fe1

@ -132,7 +132,7 @@ fn add(self, s: &str) -> String {
首先,`s2` 使用了 `&`,意味着我们使用第二个字符串的 **引用** 与第一个字符串相加。这是因为 `add` 函数的 `s` 参数:只能将 `&str``String` 相加,不能将两个 `String` 值相加。不过等一下 —— 正如 `add` 的第二个参数所指定的,`&s2` 的类型是 `&String` 而不是 `&str`。那么为什么示例 8-18 还能编译呢?
之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转***coerced*)成 `&str`。当`add`函数被调用时Rust 使用了一个被称为 **解引用强制多态***deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]`。第十五章会更深入的讨论解引用强制多态。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`
之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转***coerced*)成 `&str`。当`add`函数被调用时Rust 使用了一个被称为 **Deref 强制转换***deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]`。第十五章会更深入的讨论 Deref 强制转换。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`
其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有** 使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝,不过实际上并没有:这个实现比拷贝要更高效。

@ -5,7 +5,7 @@
实现 `Deref` trait 允许我们重载 **解引用运算符**_dereference operator_`*`(与乘法运算符或通配符相区别)。通过这种方式实现 `Deref` trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
让我们首先看看解引用运算符如何处理常规引用,接着尝试定义我们自己的类似 `Box<T>` 的类型并看看为何解引用运算符不能像引用一样工作。我们会探索如何实现 `Deref` trait 使得智能指针以类似引用的方式工作变为可能。最后,我们会讨论 Rust 的 **解引用强制多态**_deref coercions_功能以及它是如何处理引用或智能指针的。
让我们首先看看解引用运算符如何处理常规引用,接着尝试定义我们自己的类似 `Box<T>` 的类型并看看为何解引用运算符不能像引用一样工作。我们会探索如何实现 `Deref` trait 使得智能指针以类似引用的方式工作变为可能。最后,我们会讨论 Rust 的 **Deref 强制转换**_deref coercions_功能以及它是如何处理引用或智能指针的。
> 我们将要构建的 `MyBox<T>` 类型与真正的 `Box<T>` 有一个很大的区别:我们的版本不会在堆上储存数据。这个例子重点关注 `Deref`,所以其数据实际存放在何处,相比其类似指针的行为来说不算重要。
@ -154,13 +154,13 @@ Rust 将 `*` 运算符替换为先调用 `deref` 方法再进行普通解引用
注意,每次当我们在代码中使用 `*` 时, `*` 运算符都被替换成了先调用 `deref` 方法再接着使用 `*` 解引用的操作,且只会发生一次,不会对 `*` 操作符无限递归替换,解引用出上面 `i32` 类型的值就停止了,这个值与示例 15-9 中 `assert_eq!``5` 相匹配。
### 函数和方法的隐式解引用强制多态
### 函数和方法的隐式 Deref 强制转换
**解引用强制多态**_deref coercions_是 Rust 在函数或方法传参上的一种便利。其将实现了 `Deref` 的类型的引用转换为原始类型通过 `Deref` 所能够转换的类型的引用。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时,解引用强制多态将自动发生。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
**Deref 强制转换**_deref coercions_是 Rust 在函数或方法传参上的一种便利。其将实现了 `Deref` 的类型的引用转换为原始类型通过 `Deref` 所能够转换的类型的引用。当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时,Deref 强制转换将自动发生。这时会有一系列的 `deref` 方法被调用,把我们提供的类型转换成了参数所需的类型。
解引用强制多态的加入使得 Rust 程序员编写函数和方法调用时无需增加过多显式使用 `&``*` 的引用和解引用。这个功能也使得我们可以编写更多同时作用于引用或智能指针的代码。
Deref 强制转换的加入使得 Rust 程序员编写函数和方法调用时无需增加过多显式使用 `&``*` 的引用和解引用。这个功能也使得我们可以编写更多同时作用于引用或智能指针的代码。
作为展示解引用强制多态的实例,让我们使用示例 15-8 中定义的 `MyBox<T>`,以及示例 15-10 中增加的 `Deref` 实现。示例 15-11 展示了一个有着字符串 slice 参数的函数定义:
作为展示 Deref 强制转换的实例,让我们使用示例 15-8 中定义的 `MyBox<T>`,以及示例 15-10 中增加的 `Deref` 实现。示例 15-11 展示了一个有着字符串 slice 参数的函数定义:
<span class="filename">文件名: src/main.rs</span>
@ -172,7 +172,7 @@ fn hello(name: &str) {
<span class="caption">示例 15-11`hello` 函数有着 `&str` 类型的参数 `name`</span>
可以使用字符串 slice 作为参数调用 `hello` 函数,比如 `hello("Rust");`解引用强制多态使得用 `MyBox<String>` 类型值的引用调用 `hello` 成为可能,如示例 15-12 所示:
可以使用字符串 slice 作为参数调用 `hello` 函数,比如 `hello("Rust");`Deref 强制转换使得用 `MyBox<String>` 类型值的引用调用 `hello` 成为可能,如示例 15-12 所示:
<span class="filename">文件名: src/main.rs</span>
@ -205,11 +205,11 @@ fn main() {
}
```
<span class="caption">示例 15-12因为解引用强制多态,使用 `MyBox<String>` 的引用调用 `hello` 是可行的</span>
<span class="caption">示例 15-12因为 Deref 强制转换,使用 `MyBox<String>` 的引用调用 `hello` 是可行的</span>
这里使用 `&m` 调用 `hello` 函数,其为 `MyBox<String>` 值的引用。因为示例 15-10 中在 `MyBox<T>` 上实现了 `Deref` traitRust 可以通过 `deref` 调用将 `&MyBox<String>` 变为 `&String`。标准库中提供了 `String` 上的 `Deref` 实现,其会返回字符串 slice这可以在 `Deref` 的 API 文档中看到。Rust 再次调用 `deref``&String` 变为 `&str`,这就符合 `hello` 函数的定义了。
如果 Rust 没有实现解引用强制多态,为了使用 `&MyBox<String>` 类型的值调用 `hello`,则不得不编写示例 15-13 中的代码来代替示例 15-12
如果 Rust 没有实现 Deref 强制转换,为了使用 `&MyBox<String>` 类型的值调用 `hello`,则不得不编写示例 15-13 中的代码来代替示例 15-12
<span class="filename">文件名: src/main.rs</span>
@ -242,17 +242,17 @@ fn main() {
}
```
<span class="caption">示例 15-13如果 Rust 没有解引用强制多态则必须编写的代码</span>
<span class="caption">示例 15-13如果 Rust 没有 Deref 强制转换则必须编写的代码</span>
`(*m)``MyBox<String>` 解引用为 `String`。接着 `&``[..]` 获取了整个 `String` 的字符串 slice 来匹配 `hello` 的签名。没有解引用强制多态所有这些符号混在一起将更难以读写和理解。解引用强制多态使得 Rust 自动的帮我们处理这些转换。
`(*m)``MyBox<String>` 解引用为 `String`。接着 `&``[..]` 获取了整个 `String` 的字符串 slice 来匹配 `hello` 的签名。没有 Deref 强制转换所有这些符号混在一起将更难以读写和理解。Deref 强制转换使得 Rust 自动的帮我们处理这些转换。
当所涉及到的类型定义了 `Deref` traitRust 会分析这些类型并使用任意多次 `Deref::deref` 调用以获得匹配参数的类型。这些解析都发生在编译时,所以利用解引用强制多态并没有运行时惩罚!
当所涉及到的类型定义了 `Deref` traitRust 会分析这些类型并使用任意多次 `Deref::deref` 调用以获得匹配参数的类型。这些解析都发生在编译时,所以利用 Deref 强制转换并没有运行时惩罚!
### 解引用强制多态如何与可变性交互
### Deref 强制转换如何与可变性交互
类似于如何使用 `Deref` trait 重载不可变引用的 `*` 运算符Rust 提供了 `DerefMut` trait 用于重载可变引用的 `*` 运算符。
Rust 在发现类型和 trait 实现满足三种情况时会进行解引用强制多态
Rust 在发现类型和 trait 实现满足三种情况时会进行 Deref 强制转换
- 当 `T: Deref<Target=U>` 时从 `&T``&U`
- 当 `T: DerefMut<Target=U>` 时从 `&mut T``&mut U`

@ -286,7 +286,7 @@ impl Post {
接着调用 `unwrap` 方法,这里我们知道它永远也不会 panic因为 `Post` 的所有方法都确保在他们返回时 `state` 会有一个 `Some` 值。这就是一个第十二章 [“当我们比编译器知道更多的情况”][more-info-than-rustc] 部分讨论过的我们知道 `None` 是不可能的而编译器却不能理解的情况。
接着我们就有了一个 `&Box<State>`,当调用其 `content` 时,解引用强制多态会作用于 `&``Box` ,这样最终会调用实现了 `State` trait 的类型的 `content` 方法。这意味着需要为 `State` trait 定义增加 `content`,这也是放置根据所处状态返回什么内容的逻辑的地方,如示例 17-18 所示:
接着我们就有了一个 `&Box<State>`,当调用其 `content` 时,Deref 强制转换会作用于 `&``Box` ,这样最终会调用实现了 `State` trait 的类型的 `content` 方法。这意味着需要为 `State` trait 定义增加 `content`,这也是放置根据所处状态返回什么内容的逻辑的地方,如示例 17-18 所示:
<span class="filename">文件名: src/lib.rs</span>

Loading…
Cancel
Save