电子说
Pin/Unpin
。&mut T
和 &T
之间的折中。Pin<&mut T>
的重点是说:
&mut T
一样),但是&mut T
)async
时,它们会自然地出现,因为未来值往往会在引用自己的本地值。async fn self_ref() { let mut v = [1, 2, 3]; let x = &mut v[0]; tokio::from_secs(1)).await; *x = 42; }
futures
是状态机(不像闭包)。self_ref
在第一个 await
处将控制权传递回调用者。这意味着尽管 v
和 x
看起来像普通的堆栈变量,但在这里可能发生了更复杂的事情。enum SelfRefFutureState { Unresumed, // Created and wasn't polled yet. Returned, Poisoned, // `panic!`ed. SuspensionPoint1, // First `await` point. } struct SelfRefFuture { state: SelfRefFutureState, v: [i32; 3], x: &'problem mut i32, // a "reference" to an element of `self.v`, // which is a big problem if we want to move `self`. // (and we didn't even consider borrowchecking!) }
SelfRefFuture
,这会导致 x
指向无效的内存。
let f = self_ref(); let boxed_f = Box::new(f); // Evil? let mut f1 = self_ref(); let mut f2 = self_ref(); std::swap(&mut f1, &mut f2); // Blasphemy?
futures do nothing unless you这是因为调用.await
orpoll
them#[warn(unused_must_use)]
on by default – rustc
self_ref
实际上什么都不做, 我们实际上会得到类似于:
struct SelfRefFuture { state: SelfRefFutureState, v: MaybeUninit<[i32; 3]>, x: *mut i32, // a pointer into `self.v`, // still a problem if we want to move `self`, but only after it is set. // // .. other locals, like the future returned from `tokio::sleep`. }
impl SelfRefFuture { fn new() -> Self { Self { state: SelfRefFutureState::Unresumed, v: MaybeUninit::uninit(), x: std::null_mut(), // .. } } }
f
上进行轮询时,我们才会遇到自我引用的问题(x
指针被设置),但如果 f 被包裹在 Pin
中,所有这些移动都变成了 unsafe
,这正是我们想要的。 由于许多futures 一旦执行就不应该在内存中移动,只有将它们包装在 Pin
中才能安全地使用,因此与异步相关的函数往往接受 Pin<&mut T>
(假设它们不需要移动该值)。
use tokio::timeout; async fn with_timeout_once() { let f = async { 1u32 }; let _ = timeout(Duration::from_secs(1), f).await; }
&mut f
(否则会得到 use of moved value
),这将导致编译器报错
use tokio::timeout; async fn with_timeout_twice() { let f = async { 1u32 }; // error[E0277]: .. cannot be unpinned, consider using `Box::pin`. // required for `&mut impl Future ` to implement `Future`let _ = timeout(Duration::from_secs(1), &mut f).await; // An additional retry. let _ = timeout(Duration::from_secs(1), &mut f).await; }
timeout
调用了被定义为 Future::poll
的函数
fn poll(self: Pin<&mut Self>, ...) -> ... { ... }
await
f
时,我们放弃了对它的所有权。 编译器能够为我们处理固定引用,但如果我们只提供一个 &mut f
,它就无法做到这一点,因为我们很容易破坏 Pin 的不变性:
use tokio::timeout; async fn with_timeout_twice_with_move() { let f = async { 1u32 }; // error[E0277]: .. cannot be unpinned, consider using `Box::pin`. let _ = timeout(Duration::from_secs(1), &mut f).await; // .. because otherwise, we could move `f` to a new memory location, after it was polled! let f = *Box::new(f); let _ = timeout(Duration::from_secs(1), &mut f).await; }
pin!
use tokio::pin; use tokio::timeout; async fn with_timeout_twice() { let f = async { 1u32 }; pin!(f); // f is now a `Pin<&mut impl Future >`.let _ = timeout(Duration::from_secs(1), &mut f).await; let _ = timeout(Duration::from_secs(1), &mut f).await; }
f
在被 pin 包裹之后不再可访问。如果我们看不到它,就无法移动它。 事实上我们可以更准确地表达不能移动规则:指向的值在值被丢弃之前不能移动(无论何时丢弃 Pin
)。pin!
宏的作用:它确保原始的 f
对我们的代码不再可见,从而强制执行 Pin
的不变性 Tokio’s pin!
是这样实现的:
// Move the value to ensure that it is owned let mut f = f; // Shadow the original binding so that it can't be directly accessed // ever again. #[allow(unused_mut)] let mut f = unsafe { Pin::new_unchecked(&mut f) };
pin!
有点更酷,但使用的是相同的原理:用新创建的 Pin
来遮蔽原始值,使其无法再被访问和移动。
Pin
是一个指针(对另一个指针的零大小的包装器),它有点像 &mut T
但有更多的规则。 下一个问题将是“归还借用的数据”。 我们无法回到以前的固定未来
use std::Future; async fn with_timeout_and_return() -> impl Future { let f = async { 1u32 }; pin!(f); // f is now a `Pin<&mut impl Future>`.let s = async move { let _ = timeout(Duration::from_secs(1), &mut f).await; }; // error[E0515]: cannot return value referencing local variable `f` s }
f
现在是一个指针,它指向的数据(异步闭包)在我们从函数返回后将不再存在。 因此,我们可以使用 Box::pin
-pin!(f); +let mut f = Box::pin(f);
Pin<&mut T>
是 &mut T
和 &T
之间的(一个包装器)指针吗? 嗯,一个 mut Box
也像一个 &mut T
,但有所有权。 所以一个 Pin>
是一个指向可变 Box
和不可变 Box
之间的指针,值可以被修改但不能被移动。
Unpin
是一种 Trait。它不是 Pin
的"相反",因为 Pin
是指针的一种类型,而特征不能成为指针的相反。Unpin
也是一个自动特性(编译器在可能的情况下会自动实现它),它标记了一种类型,其值在被固定后可以被移动(例如,它不会自我引用)。T: Unpin
,我们总是可以 Pin::new
和 Pin::{into_inner,get_mut}
T 的值,这意味着我们可以轻松地在“常规”的可变值之间进行转换,并忽略直接处理固定值所带来的复杂性。Unpin
Trait 是 Pin
的一个重要限制,也是 Box::pin
如此有用的原因之一:当 T: !Unpin
时,“无法移动或替换 Pin>
的内部”,因此 Box::pin
(或者更准确地说是 Box::into_pin
)可以安全地调用不安全的 Pin::new_unchecked
,而得到的 Box
总是 Unpin
的,因为移动它时并不会移动实际的值。fn not_self_ref() -> impl Future u32> + Unpin { struct Trivial {} impl Future for Trivial { type Output = u32; fn poll(self: Pin<&mut Self>, _cx: &mut std::Context<'_>) -> std::Poll { std::Ready(1) } } Trivial {} }
async fn not_self_ref_with_timeout() { let mut f = not_self_ref(); let _ = timeout(Duration::from_secs(1), &mut f).await; let _ = timeout(Duration::from_secs(1), &mut f).await; }
async fn
或 async {}
语法创建的任何 Future 都被视为 !Unpin
,这意味着一旦我们将其放入 Pin
中,就无法再取出来。
Pin
是对另一个指针的包装,有点像 &mut T
,但额外的规则是在值被丢弃之前,移动它所指向的值是不安全的。Pin
)。&mut T
的能力并破坏 Pin
的不变性的情况下创建它。await
Future 时,编译器可以处理固定,因为它知道一旦所有权转移, Future
就不会移动。pin!
或 Box::pin
)Unpin
是一个标记特征,表示一个类型即使在被包装在 Pin
之后仍然可以安全地移动,使一切变得更简单。Unpin
,但 async fn
和 async {}
总是产生 !Unpin
结构。全部0条评论
快来发表一下你的评论吧 !