为了轮询 futures,它们必须被一个称为Pin<T>
的特殊类型固定。如果你已经阅读了以前的章节-Executing Futures and Tasks
的the Future trait
小节,你将在Future::poll
方法中定义的self::Pin<&mut Self>
认识到Pin
。但是它是什么意思,我们为什么需要它?
Pin
与Unpin
标记协同工作。固定保证实现了!Unpin
的对象永远不会被move。为了理解为什么它是必要的,我们需要记住async
/.await
是怎么工作的。考虑以下代码:
let fut_one = /* ... */;
let fut_two = /* ... */;
async move {
fut_one.await;
fut_two.await;
}
在底层,这个创建了一个匿名类型,它实现了Future
trait,提供了一个poll
方法,就像这样:
// The `Future` type generated by our `async { ... }` block
struct AsyncFuture {
fut_one: FutOne,
fut_two: FutTwo,
state: State,
}
// List of states our `async` block can be in
enum State {
AwaitingFutOne,
AwaitingFutTwo,
Done,
}
impl Future for AsyncFuture {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
loop {
match self.state {
State::AwaitingFutOne => match self.fut_one.poll(..) {
Poll::Ready(()) => self.state = State::AwaitingFutTwo,
Poll::Pending => return Poll::Pending,
}
State::AwaitingFutTwo => match self.fut_two.poll(..) {
Poll::Ready(()) => self.state = State::Done,
Poll::Pending => return Poll::Pending,
}
State::Done => return Poll::Ready(()),
}
}
}
}
当poll
第一次被调用,它将轮训fut_one
。如果fut_one
还未完成,AsyncFuture::poll
将会返回。Future 对poll
进行调用,将在上一个停止的地方继续。这个过程持续到futures能够成功完成为止。
然而,如果我有一个使用了引用的async
块,将会发生什么呢?例如:
async {
let mut x = [0; 128];
let read_into_buf_fut = read_into_buf(&mut x);
read_into_buf_fut.await;
println!("{:?}", x);
}
这个编译成什么结构呢?
struct ReadIntoBuf<'a> {
buf: &'a mut [u8], // points to `x` below
}
struct AsyncFuture {
x: [u8; 128],
read_into_buf_fut: ReadIntoBuf<'what_lifetime?>,
}
这里,ReadIntoBuf
future从我们结构体的字段x
获取了引用。然而,如果AsyncFuture
被 move 了,本地的x
也将会move,使存储在read_into_buf_fut.buf
的指针失效。
把futures固定在内存的一个特定的地方来防止这个问题,使在一个async
块中创建一个引用变得安全。
让我们通过使用一个稍微简单的例子来尝试理解固定。以上我们遇到的问题最终归结为我们怎么在自指类型(self-referential types)中处理引用。
现在我们的例子像这样:
use std::pin::Pin;
#[derive(Debug)]
struct Test {
a: String,
b: *const String,
}
impl Test {
fn new(txt: &str) -> Self {
Test {
a: String::from(txt),
b: std::ptr::null(),
}
}
fn init(&mut self) {
let self_ref: *const String = &self.a;
self.b = self_ref;
}
fn a(&self) -> &str {
&self.a
}
fn b(&self) -> &String {
unsafe {&*(self.b)}
}
}
Test
提供了方法来获取字段a
和b
的引用。因为b
是一个a
的引用,且rust的借用规则不允许我们定义它的生命周期,因此我们将它存储为指针。我们现在有一个自指的结构体了。
如果我们没有移动任何数据,示例将运行良好,如通过运行下面的示例可以观察到的:
fn main() {
let mut test1 = Test::new("test1");
test1.init();
let mut test2 = Test::new("test2");
test2.init();
println!("a: {}, b: {}", test1.a(), test1.b());
println!("a: {}, b: {}", test2.a(), test2.b());
}
我们获得了我们期待的结果:
a: test1, b: test1
a: test2, b: test2
如果我们通过交换test1
和test2
,从而move数据,会发生什么呢:
fn main() {
let mut test1 = Test::new("test1");
test1.init();
let mut test2 = Test::new("test2");
test2.init();
println!("a: {}, b: {}", test1.a(), test1.b());
std::mem::swap(&mut test1, &mut test2);
println!("a: {}, b: {}", test2.a(), test2.b());
}
天真地,我们可能会认为将会活动test1
的调试打印2次:
a: test1, b: test1
a: test1, b: test1
但是实际上:
a: test1, b: test1
a: test1, b: test2
test2.b
的指针始终指向test1
内部的旧位置。这个结构体不再是自指的了,它持有不同对象的字段的指针。这意味着我们不能依靠test2.b
的生命周期来帮的test2
的生命周期。
如果你还没理解,下面的例子将最终让你理解:
fn main() {
let mut test1 = Test::new("test1");
test1.init();
let mut test2 = Test::new("test2");
test2.init();
println!("a: {}, b: {}", test1.a(), test1.b());
std::mem::swap(&mut test1, &mut test2);
test1.a = "I've totally changed now!".to_string();
println!("a: {}, b: {}", test2.a(), test2.b());
}
下面的图形将发生的一切形象化:
很容易使它显示未定义的行为并以其他引人注目的方式失败。
让我们看看Pin
类型怎么帮助我们解决固定的问题。
Pin
类型包装了指针类型,保证指针背后的值不会被移动。例如:Pin<&mut T>
、Pin<&T>
、Pin<Box<T>>
都能保证如果 T:!Unpin
,T
将不会被move。
多数类型在被移动时都没问题。这些类型实现了一个叫做Unpin
的trait。Unpin
类型的指针能自由放置到Pin
或从Pin
中取出。例如,u8
是Unpin
,因此Pin<&mut u8>
的行为和普通的&mut u8
一样。
然而,被一个!Unpin
固定的类型不能被移动。通过async/await
创建的Futures就是这样的一个例子。
回到我们的例子。我们可以通过使用Pin
解决我们的问题。让我们看看例子通过固定指针替换了是什么样的。
use std::pin::Pin;
use std::marker::PhantomPinned;
#[derive(Debug)]
struct Test {
a: String,
b: *const String,
_marker: PhantomPinned,
}
impl Test {
fn new(txt: &str) -> Self {
Test {
a: String::from(txt),
b: std::ptr::null(),
_marker: PhantomPinned, // This makes our type `!Unpin`
}
}
fn init<'a>(self: Pin<&'a mut Self>) {
let self_ptr: *const String = &self.a;
let this = unsafe { self.get_unchecked_mut() };
this.b = self_ptr;
}
fn a<'a>(self: Pin<&'a Self>) -> &'a str {
&self.get_ref().a
}
fn b<'a>(self: Pin<&'a Self>) -> &'a String {
unsafe { &*(self.b) }
}
}
如果我们的类型实现了!Unpin
,将对象固定到栈上将总是unsafe
的。当你固定对象在栈上是,你可以使用像pin_utils
这样的crate来避免写unsafe
的代码。
下面,我们固定对象test1
和test2
到栈上。
pub fn main() {
// test1 is safe to move before we initialize it
let mut test1 = Test::new("test1");
// Notice how we shadow `test1` to prevent it from being accessed again
let mut test1 = unsafe { Pin::new_unchecked(&mut test1) };
Test::init(test1.as_mut());
let mut test2 = Test::new("test2");
let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };
Test::init(test2.as_mut());
println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));
println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));
}
现在,如果我们尝试移动我们的数据,我们将会得到一个编译错误。
pub fn main() {
let mut test1 = Test::new("test1");
let mut test1 = unsafe { Pin::new_unchecked(&mut test1) };
Test::init(test1.as_mut());
let mut test2 = Test::new("test2");
let mut test2 = unsafe { Pin::new_unchecked(&mut test2) };
Test::init(test2.as_mut());
println!("a: {}, b: {}", Test::a(test1.as_ref()), Test::b(test1.as_ref()));
std::mem::swap(test1.get_mut(), test2.get_mut());
println!("a: {}, b: {}", Test::a(test2.as_ref()), Test::b(test2.as_ref()));
}
类型系统阻止了我们移动数据。
记住栈固定总是依赖你编写的unsafe
代码提供的保证。
虽然我们知道&'a mut T
的指针在'a
的生命周期中固定,但我们不知道'a
结束后数据&'a mut T
指向的数据是否没有移动。 如果这样做,将违反Pin
的约定。
容易犯的一个错误就是忘记隐藏原始变量,因为您可以drop Pin
并将数据移动到&'a mut T
之后,如下所示(这违反了Pin
约定):
fn main() {
let mut test1 = Test::new("test1");
let mut test1_pin = unsafe { Pin::new_unchecked(&mut test1) };
Test::init(test1_pin.as_mut());
drop(test1_pin);
println!(r#"test1.b points to "test1": {:?}..."#, test1.b);
let mut test2 = Test::new("test2");
mem::swap(&mut test1, &mut test2);
println!("... and now it points nowhere: {:?}", test1.b);
}
固定一个!Unpin
类型到堆上,给予我们的数据一个稳定的地址,因此我们知道指向的数据在它被固定后不能被移动。在栈固定的约定中,我们知道知道对象在生命周期内将会被固定。
use std::pin::Pin;
use std::marker::PhantomPinned;
#[derive(Debug)]
struct Test {
a: String,
b: *const String,
_marker: PhantomPinned,
}
impl Test {
fn new(txt: &str) -> Pin<Box<Self>> {
let t = Test {
a: String::from(txt),
b: std::ptr::null(),
_marker: PhantomPinned,
};
let mut boxed = Box::pin(t);
let self_ptr: *const String = &boxed.as_ref().a;
unsafe { boxed.as_mut().get_unchecked_mut().b = self_ptr };
boxed
}
fn a<'a>(self: Pin<&'a Self>) -> &'a str {
&self.get_ref().a
}
fn b<'a>(self: Pin<&'a Self>) -> &'a String {
unsafe { &*(self.b) }
}
}
pub fn main() {
let mut test1 = Test::new("test1");
let mut test2 = Test::new("test2");
println!("a: {}, b: {}",test1.as_ref().a(), test1.as_ref().b());
println!("a: {}, b: {}",test2.as_ref().a(), test2.as_ref().b());
}
一个函数需要Unpin
的futures。为了在需要一个Unpin
类型的函数中使用不是Unpin
的Future
或者Stream
,你可以使用Box::pin
(创建一个Pin<Box<T>>
),或者pin_utils::pin_mut!
宏(创建一个Pin<&mut T>
)来获取一个固定的值。Pin<Box<Fut>>
和Pin<&mut Fut>
都可以作为Future使用,也都实现了Unpin
。
例如:
use pin_utils::pin_mut; // `pin_utils` is a handy crate available on crates.io
// A function which takes a `Future` that implements `Unpin`.
fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { /* ... */ }
let fut = async { /* ... */ };
execute_unpin_future(fut); // Error: `fut` does not implement `Unpin` trait
// Pinning with `Box`:
let fut = async { /* ... */ };
let fut = Box::pin(fut);
execute_unpin_future(fut); // OK
// Pinning with `pin_mut!`:
let fut = async { /* ... */ };
pin_mut!(fut);
execute_unpin_future(fut); // OK
- 如果
T: Unpin
(这是默认的),Pin<'a, T>
完全与&'a mut T
相等。换句话说:Unpin
意味着即使此类型被固定,它也能被移动,因此Pin
在此类型上没有作用。 - 如果
T: !Unpin
,将&mut T
固定为T
需要unsafe
。 - 很斗殴标准库的类型实现了
Unpin
。你遇到的大多数常规类型也是如此。async/await
产生的Future
是一个例外。 - 在 nightly版本中,通过一个特性标志(feature flag),你可以在一个类型上添加一个
!Unpin
bound,或者在稳定版中,添加std::marker::PhantomPinned
到你的类型上。 - 你可以将数据固定在栈或者堆上。
- 固定一个
!Unpin
对象到栈上需要unsafe
。 - 固定一个
!Unpin
对象到堆上不需要unsafe
。使用Box::pin
能做到这个。 - 对于固定
T: !Unpin
的数据,你必须保持不变,即从它被固定到调用drop
这段时间中,内存不会失效或从新定位。这对于Pin
约定是很重要的部分。