mirror of
https://github.com/KaiserY/trpl-zh-cn
synced 2025-04-29 09:48:09 +08:00
update ch17-05
This commit is contained in:
parent
50a358a2ae
commit
7297546307
@ -134,11 +134,11 @@ pub trait Future {
|
||||
|
||||
回忆一下 `await` 的实现是基于对 `poll` 的调用,这有助于解释之前见到的错误信息,不过那是关于 `Unpin` 的。所以 `Pin` 具体与 `Unpin` 有何关联,又为什么 `Future` 需要 `self` 在一个 `Pin` 类型中才能调用 `poll`?
|
||||
|
||||
回忆一下本章之前 future 中一系列的 await point 被编译为一个状态机,而编译器确保这个状态机遵守所有 Rust 在安全方面的正常规则,包括借用和所有权。为了使其正常工作,Rust 检查在一个 await point 和另一个 await point 之间或到异步代码块结尾之间什么数据是需要的。它接着在编译的状态机中创建一个相应的变体。每一个变体获得其在源码中对应片段所用到的数据的访问权限,要么获取数据的所有权要么获取其可变或不可变引用。
|
||||
回忆一下本章之前 future 中一系列的 await point 被编译为一个状态机,而编译器确保这个状态机遵守 Rust 在安全方面的所有常规规则,包括借用和所有权。为了使其正常工作,Rust 检查在一个 await point 和另一个 await point 之间或到异步代码块结尾之间什么数据是需要的。编译器接着在生成的状态机中创建一个相应的变体。每一个变体获得其在源码中对应片段所用到的数据的访问权限,要么获取数据的所有权要么获取其可变或不可变引用。
|
||||
|
||||
到目前为止, 一切正常:如果你在给定异步代码块中搞错了所有权或者引用,借用检查器会告诉你。当我们需要移动对应代码块的 future -- 例如将其移动到 `Vec` 中或者传递给 `join_all` -- 事情就会变得棘手。
|
||||
到目前为止,一切正常:如果你在给定异步代码块中搞错了所有权或者引用,借用检查器会告诉你。当我们需要移动对应代码块的 future -- 例如将其移动到 `Vec` 中或者传递给 `join_all` -- 问题就会变得棘手。
|
||||
|
||||
当我们移动一个 future -- 将其移动进一个数据结构通过 `join_all` 将其用作迭代器或者将其从函数返回 -- 这实际上意味着将 Rust 创建的状态机移动给我们。同时不同于 Rust 中大部分其它类型,Rust 为异步代码块创建的 future 可能最终会在任意给定变体的字段中有其自身的引用,如图 17-4 中的简化图所示。
|
||||
当我们移动一个 future -- 将其移动进一个数据结构通过 `join_all` 将其用作迭代器或者将其从函数返回 -- 这实际上意味着要移动 Rust 为我们创建的状态机。而与 Rust 中大多数其他类型不同,Rust 为异步代码块创建的 future 可能最终会在任意给定变体的字段中包含对其自身的引用,如图 17-4 中的简化图所示。
|
||||
|
||||
<figure>
|
||||
|
||||
@ -148,7 +148,7 @@ pub trait Future {
|
||||
|
||||
</figure>
|
||||
|
||||
但是,默认移动任何拥有其自身引用的对象是不安全(unsafe)的,因为引用总是会指向任何其引用数据的实际内存地址(见图 17-5)。如果我们移动数据结构本身,这些内部引用会停留在指向老的地址。然后,这些内存地址现在是无效的。一方面,当修改这些数据结构时这些值不会被更新。另一个更重要的方面是,电脑现在可以随意复用这些内存用于其它用途!之后你最终可能会读取到完全不相关的数据。
|
||||
但是,默认情况下移动任何拥有其自身引用的对象是不安全(unsafe)的,因为引用总是会指向任何其引用数据的实际内存地址(见图 17-5)。如果我们移动数据结构本身,这些内部引用会停留在指向老的地址。然而,这些内存地址现在是无效的。一方面,当修改这些数据结构时这些值不会被更新。更重要的是,计算机现在可以随意将这块内存重新用于其他用途!之后你最终可能会读取到完全不相关的数据。
|
||||
|
||||
<figure>
|
||||
|
||||
@ -158,9 +158,9 @@ pub trait Future {
|
||||
|
||||
</figure>
|
||||
|
||||
理论上来说,Rust 编译器也可以在对象被移动时尝试更新其所有的引用,不过这会增加很多性能开销,特别是在有一整个网状的引用需要更新的时候。相反如果我们确保相关的数据结构**不能再内存中移动**,我们就无需更新任何引用。这正是 Rust 的借用检查器所要求的:在安全代码中,禁止移动任何有自身活动引用的项。
|
||||
理论上来说,Rust 编译器也可以在对象被移动时尝试更新其所有的引用,不过这会增加很多性能开销,特别是在有一整个网状的引用需要更新的时候。相反如果我们确保相关的数据结构**不会再内存中移动**,就无需更新任何引用。这正是 Rust 的借用检查器所要求的:在安全代码中,禁止移动任何有自身活动引用的项。
|
||||
|
||||
构建于这之上的 `Pin` 正好给了我们所需的保证。当我们通过 `Pin` 封装一个值的引用来 **pin** 住它时,它就不能再移动了。也就是说,如果有 `Pin<Box<SomeType>>`,你实际上 ping 住了 `SomeType` 的值,而**不是** `Box` 指针。图 17-6 解释了这一过程。
|
||||
而 `Pin` 正是在此基础上,为我们提供了所需的保证。当我们通过 `Pin` 封装一个值的引用来 **pin** 住它时,它就无法再移动了。也就是说,如果有 `Pin<Box<SomeType>>`,你实际上 pin 住了 `SomeType` 的值,而**不是** `Box` 指针。图 17-6 解释了这一过程。
|
||||
|
||||
<figure>
|
||||
|
||||
@ -170,7 +170,7 @@ pub trait Future {
|
||||
|
||||
</figure>
|
||||
|
||||
事实上,`Box` 指针仍然可以随意移动。请记住:我们关心确保最终被引用的数据保持不动。如果指针移动了,**但是它指向的数据还在相同的位置**,就像图 17-7 一样,就不会有潜在的问题。(作为一个独立的练习,查看一下 `Pin` 类型以及 `std::pin` 模块的文档来尝试理解如何通过 `Pin` 封装一个 `Box` 来做到这些。)其关键在于自引用类型本身不可移动,因为它仍然是被 pin 住的。
|
||||
事实上,`Box` 指针仍然可以随意移动。请记住:我们关心确保最终被引用的数据保持不动。如果指针移动了,**但是它指向的数据还在相同的位置**,就像图 17-7 一样,就不会有潜在的问题。(作为一个独立的练习,可以查看相关类型以及 `std::pin` 模块的文档,尝试推导出如何使用一个包裹 `Box` 的 `Pin` 来实现这一点。)其关键在于自引用类型本身不可移动,因为它仍然是被 pin 住的。
|
||||
|
||||
<figure>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user