Pin的出现主要是为了解决自引用问题
自引用问题
Test 类型提供了方法,来获取字段 a 或 b 的引用。因为 b 是指向 a 的引用,但由于 Rust 的借用规则,我们不能定义它的生命周期(lifetime),所以我们把它存成指针。这就是一个自引用结构体
#![allow(unused)]
fn main() {
#[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) }
}
}
}
如果不把我们的数据四处转移,我们的例子可以运行得很好:
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 交换了,会发生什么:
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());
}
我们得到得结果是:
a: test1, b: test1
a: test1, b: test2
其根本原因是test1和test2在内存中的位置变了
Pin的使用
- 如果
T: Unpin(默认会实现),那么Pin<'a, T>完全等价于&'a mut T。换言之:Unpin意味着这个类型被移走也没关系,就算已经被固定了,所以Pin对这样的类型毫无影响。 - 如果
T: !Unpin, 获取已经被固定的 T 类型示例的&mut T需要 unsafe。 - 标准库中的大部分类型实现
Unpin,在 Rust 中遇到的多数“平常”的类型也是一样。但是, async/await 生成的Future是个例外。 - 你可以在 nightly 通过特性标记来给类型添加
!Unpin约束,或者在 stable 给你的类型加std::marker::PhatomPinned字段。 - 你可以将数据固定到栈上或堆上
- 固定
!Unpin对象到栈上需要unsafe - 固定
!Unpin对象到堆上不需要unsafe。Box::pin可以快速完成这种固定
固定到栈上
回到我们的例子。我们能用 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 的库来在将数据固定到栈上的时候避免写 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的 被指向对象(pointee) 在生命周期'a期间固定,我们不知道被&'a mut T指向数据是否在'a结束后仍然不被移动。如果移动了,将会违反固定的协约。另外一个常见错误是忘记遮蔽(shadow)原本的变量,因为你可以释放
Pin然后移动数据到&'a mut T,像下面这样(这违反了固定的协约):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.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 test1 = Test::new("test1");
let 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());
}
一些函数需要他们协作的 future 是 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