固定

为了能对 futures 进行轮询,必须使用一个特殊的类型 Pin<T> 来固定它们。 如果你看了"执行 Future 和任务"这章中Future 特征一节, 你会在 Future::poll 定义的方法里看到对 Pin<T> 的使用:self: Pin<&mut Self>。 但这是什么意思,我们又为什么要使用它呢?

为什么要固定

PinUnpin 标记协同工作。固定可以保证实现了 !Unpin 的对象永远不会被移动!为了理解这样做的必要性,不妨让我们回忆一下, async/.await 是如何工作的。请看下面的代码:

let fut_one = /* ... */;
let fut_two = /* ... */;
async move {
    fut_one.await;
    fut_two.await;
}

在内部,它创建了一个实现了 Future 的匿名类型,并提供了一个 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 调用会在之前停止的地方的继续。 此过程一直持续到 future 完成为止。

但是,如果我们在一个 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 被移动了,x 的位置也一定会移动, 从而导致存储在 read_into_buf_fut.buf 中的指针变为无效指针!

将 futures 固定在内存中特定的位置可以避免此问题, 从而安全地在 async 代码块中创建对值的引用。

固定的细节

让我们尝试着通过一个稍简单的示例来更好地理解固定。我们在上面遇到的问题, 归根结底是我们要如何在 Rust 中处理自引用类型中的引用。

目前我们的示例看起来是这样的:

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 {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

Test 提供了获取字段 ab 的引用的方法。因为 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());

}
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(),
        }
    }

    // We need an `init` method to actually set our self-reference
    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 {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

这正是我们所期望的结果:

a: test1, b: test1
a: test2, b: test2

让我们看看如果我们将 testtest2 互换(移动数据)会发生什么:

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());

}
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 {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

我们天真地以为 test1 会被打印两次:

a: test1, b: test1
a: test1, b: test1

但我们看到的却是:

a: test1, b: test1
a: test1, b: test2

现在,本该指向 test2.b 的指针依然指向了 test1 内部的旧地址。 结构体不再是自引用了,它持有一个指向其它对象中字段的指针。 这意味着我们无法保证 test2.btest2 的生命周期之间存在关联关系!

如果你仍不相信,那这至少可以说服你:

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());

}
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 {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

下面的图表可以帮助你了解到底发生了什么:

图1: 交换前后 swap_problem

通过它可以很容易看出未定义行为及其导致的错误。

固定:实践

让我们来探究下如何固定,以及 Pin 类型如何帮助我们解决这个问题。

Pin 类型用于装饰指针类型,来确保若指针指向的值未实现 Unpin,则其不能被移动。 例如,如果有 T: !Unpin,则 Pin<&mut T>Pin<&T>Pin<Box<T>> 都可保证 T 无法被移动。

大部分类型在被移动时都没有问题。因为这些类型实现 Unpin 特征。 指向 Unpin 类型的指针都可以自由地放入 Pin 或从中取出来。比如, u8Unpin,所以 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(self: Pin<&mut Self>) {
        let self_ptr: *const String = &self.a;
        let this = unsafe { self.get_unchecked_mut() };
        this.b = self_ptr;
    }

    fn a(self: Pin<&Self>) -> &str {
        &self.get_ref().a
    }

    fn b(self: Pin<&Self>) -> &String {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

如果我们的类型实现了 !Unpin,在栈上固定一个对象总是 unsafe 的。 你可以通过使用像 pin_utils 这样的 crate 来避免固定到栈时自己写 unsafe 代码。

下面,我们将 test1test2 固定在栈上:

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()));
}
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(),
            // This makes our type `!Unpin`
            _marker: PhantomPinned,
        }
    }
    fn init(self: Pin<&mut Self>) {
        let self_ptr: *const String = &self.a;
        let this = unsafe { self.get_unchecked_mut() };
        this.b = self_ptr;
    }

    fn a(self: Pin<&Self>) -> &str {
        &self.get_ref().a
    }

    fn b(self: Pin<&Self>) -> &String {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

现在,如果我们尝试移动数据,就会得到一个编译错误:

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()));
}
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(self: Pin<&mut Self>) {
        let self_ptr: *const String = &self.a;
        let this = unsafe { self.get_unchecked_mut() };
        this.b = self_ptr;
    }

    fn a(self: Pin<&Self>) -> &str {
        &self.get_ref().a
    }

    fn b(self: Pin<&Self>) -> &String {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.b) }
    }
}

类型系统阻止我们对数据进行移动,像这样:

error[E0277]: `PhantomPinned` cannot be unpinned
   --> src\test.rs:56:30
    |
56  |         std::mem::swap(test1.get_mut(), test2.get_mut());
    |                              ^^^^^^^ within `test1::Test`, the trait `Unpin` is not implemented for `PhantomPinned`
    |
    = note: consider using `Box::pin`
note: required because it appears within the type `test1::Test`
   --> src\test.rs:7:8
    |
7   | struct Test {
    |        ^^^^
note: required by a bound in `std::pin::Pin::<&'a mut T>::get_mut`
   --> <...>rustlib/src/rust\library\core\src\pin.rs:748:12
    |
748 |         T: Unpin,
    |            ^^^^^ required by this bound in `std::pin::Pin::<&'a mut T>::get_mut`

要注意,在栈上固定将始终依赖于你在写 unsafe 代码时提供的保证,这很重要。 虽然我们知道 &'a mut T 的指针在 'a 的生命周期内被固定了,但我们不知道 'a 的生命周期结束后它是否被移动了。如果这样做,就违反了 Pin 的原则。

一个很容易犯的错误是忘记隐藏原始变量,因为你可以删除 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);
}
use std::pin::Pin;
use std::marker::PhantomPinned;
use std::mem;

#[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(),
            // This makes our type `!Unpin`
            _marker: PhantomPinned,
        }
    }
    fn init(self: Pin<&mut Self>) {
        let self_ptr: *const String = &self.a;
        let this = unsafe { self.get_unchecked_mut() };
        this.b = self_ptr;
    }

    fn a(self: Pin<&Self>) -> &str {
        &self.get_ref().a
    }

    fn b(self: Pin<&Self>) -> &String {
        assert!(!self.b.is_null(), "Test::b called without Test::init being called first");
        unsafe { &*(self.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(self: Pin<&Self>) -> &str {
        &self.get_ref().a
    }

    fn b(self: Pin<&Self>) -> &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());
}

一些函数要求它们使用的 futures 必须是 Unpin(非固定)的。想要非 UnpinFutureStream 和要求 Unpin 类型的函数一起使用, 首先你需要使用 Box::pin(创建 Pin<Box<T>>)或 pin_utils::pin_mut! 宏(创建 Pin<&mut T>)固定值。Pin<Box<Fut>>Pin<&mut Fut> 都可作为 futures 使用,并且都实现了 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

总结

  1. 如果 T: Unpin(默认实现),那么 Pin<'a, T> 完全等同于 &'a mut T。 换句话说,Unpin 意味着即使被固定,此类型也可以被移动,Pin 对于这种类型是无效的。

  2. 如果 T: !Unpin,将 &mut T 转换为固定的 T 是不安全的,需要 unsafe

  3. 大部分标准库类型都实现了 Unpin。你在 Rust 中使用的大多数“正常”类型亦如此。 由 async/await 生成的 Future 则是例外。

  4. 你可以在 nightly 的 Rust 版本里,在类型上添加 !Unpin 绑定, 或者在稳定版上添加 std::marker::PhantomPinned 到你的类型。

  5. 你可以把数据固定在栈或者堆上。

  6. 将一个 !Unpin 对象固定在栈上需要是 unsafe 的。

  7. !Unpin 对象固定在堆上不需要 unsafe,使用 Box::pin 即可方便的完成。

  8. 固定 T: !Unpin 的数据时,你必须保证它的不可变性,即从被固定到数据被 drop, 它的内存不会失效或被重新分配。这是 Pin 的使用规则中的重要部分。