固定(Pinning)

为了轮询future,future首先要用特殊类型Pin<T>来固定。如果你读了前面《执行Future与 任务小节中关于Future退出的解释,你会从Future::poll方法的定义中认出Pin。但这意味 什么?我们为什么需要它?

为什么需要固定

固定保证对象永不移动。为了理解这为什么必须,我们回忆一下async/.await怎么工作吧。 考虑以下代码:


# #![allow(unused_variables)]
#fn main() {
let fut_one = ...;
let fut_two = ...;
async move {
    fut_one.await;
    fut_two.await;
}
#}

这段代码实际上创建了一个实现了Future特质的匿名类型,提供了poll方法,如下:


# #![allow(unused_variables)]
#fn main() {
// 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 {
    fn poll(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就会返回。调用poll的Future会从上次中断的地方继续。这个过程会持续 到future成功完成。

然而,如果我们在async块中用了引用呢?例如:


# #![allow(unused_variables)]
#fn main() {
async {
    let mut x = [0; 128];
    let read_into_buf_fut = read_into_buf(&mut x);
    read_into_buf_fut.await;
    println!("{:?}", x);
}
#}

这会编译成什么结构呢?


# #![allow(unused_variables)]
#fn main() {
struct ReadIntoBuf<'a> {
    buf: &'a mut [u8], // points to `x` below
}

struct AsyncFuture {
    x: [u8; 128],
    read_into_buf_fut: ReadIntoBuf<'what_lifetime?>,
}
#}

这里,ReadIhtoBuffuture持有了一个指向其他字段x的引用。然而,如果AsyncFuture被 移动了,x的位置(location)也会被移走,使得存储在read_into_buf_fut.buf的指针失效。

固定future到内存特定位置则阻止了这种问题,让创建指向async块的引用变得安全。

如何固定?

Pin类型包装了指针类型,保证了指针指向的值不会被移走。例如,Pin<&mut T>Pin<&T>Pin<Box<T>>全都保证了T不会被移走。

多数类型被移走也不会有问题。这些类型实现了Unpin特质。指向Unpin类型的指针能够自由地 放进Pin,或取走。例如,u8Unpin的,所以Pin<&mut T>的行为就像普通的&mut T

一些函数需要他们协作的future是Unpin的。为了让这些函数使用不是UnpinFutureStream,你首先需要这个值固定,要么用Box::pin(创建Pin<Box<T>>)要么使用 pin_utils::pin_mut!(创建Pin<&mut T>)。Pin<Box<Fut>>Pin<&mut Fut>都能 用作future,并且都实现了Unpin

例如:


# #![allow(unused_variables)]
#fn main() {
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
#}