固定
为了能对 futures 进行轮询,必须使用一个特殊的类型 Pin<T>
来固定它们。
如果你看了"执行 Future
和任务"这章中Future
特征一节,
你会在 Future::poll
定义的方法里看到对 Pin<T>
的使用:self: Pin<&mut Self>
。
但这是什么意思,我们又为什么要使用它呢?
为什么要固定
Pin
与 Unpin
标记协同工作。固定可以保证实现了 !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
提供了获取字段 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()); } 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
让我们看看如果我们将 test
和 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); 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.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()); } 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: 交换前后
通过它可以很容易看出未定义行为及其导致的错误。
固定:实践
让我们来探究下如何固定,以及 Pin
类型如何帮助我们解决这个问题。
Pin
类型用于装饰指针类型,来确保若指针指向的值未实现 Unpin
,则其不能被移动。
例如,如果有 T: !Unpin
,则 Pin<&mut T>
、Pin<&T>
、Pin<Box<T>>
都可保证 T
无法被移动。
大部分类型在被移动时都没有问题。因为这些类型实现 Unpin
特征。
指向 Unpin
类型的指针都可以自由地放入 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(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
代码。
下面,我们将 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())); } 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
(非固定)的。想要非 Unpin
的 Future
或 Stream
和要求 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
总结
-
如果
T: Unpin
(默认实现),那么Pin<'a, T>
完全等同于&'a mut T
。 换句话说,Unpin
意味着即使被固定,此类型也可以被移动,Pin
对于这种类型是无效的。 -
如果
T: !Unpin
,将&mut T
转换为固定的 T 是不安全的,需要unsafe
。 -
大部分标准库类型都实现了
Unpin
。你在 Rust 中使用的大多数“正常”类型亦如此。 由 async/await 生成的 Future 则是例外。 -
你可以在 nightly 的 Rust 版本里,在类型上添加
!Unpin
绑定, 或者在稳定版上添加std::marker::PhantomPinned
到你的类型。 -
你可以把数据固定在栈或者堆上。
-
将一个
!Unpin
对象固定在栈上需要是unsafe
的。 -
将
!Unpin
对象固定在堆上不需要unsafe
,使用Box::pin
即可方便的完成。 -
固定
T: !Unpin
的数据时,你必须保证它的不可变性,即从被固定到数据被 drop, 它的内存不会失效或被重新分配。这是 Pin 的使用规则中的重要部分。