简介
其中 Pin
是为了标识某个变量不能在内存中移动,而 Unpin
则允许移动。考虑异步编程中引用了某个变量,例如:
async {
let mut data = [0; 256];
let fetch_future = fetch_data(&mut data);
fetch_future.await;
println!("{:?}", data);
}
会转换为类似如下的数据结构。
struct DataBuff<'a> {
buff: &'a mut[u8], // 指向下面的data
}
struct AsyncFuture {
data: [u8; 256];
fetch_future: DataBuff<'_>,
}
也就是在结构体内部会有指针指向自己,如果 AsyncFuture
被移动,那么对应的 data
地址也发生了变动,但是,引用地址却没有修改,从而会导致不合法访问。解决方式就是让 Future
不移动即可。
基本概念
在 Rust 1.33
版本中引入了 Pin 相关的 API 实现,包括了:std::pin::Pin
std::marker::{Unpin, PhantomPinned}
impl !Unpin for T
,部分实现如下。
// core/src/pin.rs
pub struct Pin<Ptr> {
pub __pointer: Ptr,
}
impl<Ptr: Deref> Deref for Pin<Ptr> {
type Target = Ptr::Target;
fn deref(&self) -> &Ptr::Target {
Pin::get_ref(Pin::as_ref(self))
}
}
impl<Ptr: DerefMut<Target: Unpin>> DerefMut for Pin<Ptr> {
fn deref_mut(&mut self) -> &mut Ptr::Target {
Pin::get_mut(Pin::as_mut(self))
}
}
pub auto trait Unpin {}
pub struct PhantomPinned;
impl !Unpin for PhantomPinned {}
Pin
是一个结构体,并实现了 Deref
DerefMut
特征,这也就意味着是一个智能指针,其内部只能包含一个指针 (只能是指针),假设其指向的是 T
类型,只要 T
没有实现 Unpin
特征,那么就能够保证 T
永远不会被移动。
这也就意味着,如果 T
实现了 Unpin
特征,那么 Pin
的作用也就完全失效了,此时 Pin<P<T>>
就等价于 P<T>
了。
但是编译器会默认给所有类实现 Unpin
特征,其中有两个例外,实现的是 !Unpin
:A) PhantomPinned
以及作为成员的类;B) 为 async
自动生成的 impl Future
结构体。后者很容易理解,而前者实际就是为了用户可以 Pin
自定义类型。
使用 Safe Rust
的时候,不被移动的核心原理就是,避免暴露可变指针 &mut
。
示例
当某个类型声明了为 Pin
之后,可以确保实现 !Unpin
的结构体不发生移动。
use std::{marker::PhantomPinned, pin::Pin};
struct Data(u32);
impl Data {
fn dump(&self) {
println!("{} {:p}", self.0, self);
}
}
struct PinData(u32, PhantomPinned);
impl PinData {
// 通过Pin能确保打印的是相同值
fn dump(self: Pin<&Self>) {
println!("{} {:p}", self.0, self);
}
}
fn main() {
let heap = Box::new(Data(42)); // on heap
heap.dump();
let stack = *heap; // moved back to stack
stack.dump();
let pinned = Box::pin(Data(42));
pinned.as_ref().dump();
let unpined = Pin::into_inner(pinned);
let stack = *unpined;
let pinned = Box::pin(stack);
pinned.as_ref().dump();
let pinned = Box::pin(PinData(42, PhantomPinned));
pinned.as_ref().dump();
//let unpined = Pin::into_inner(pinned); // 这里执行会失败
}
总结
- 实现了
Unpin
特征,可以通过Pin::get_mut()
或者&mut T
(因为实现了DerefMut
特征) 获取,不确定具体使用场景。 - 如果不想自定义结构体移动,添加
_marker: PhantomPinned
即可,或者应用nightly
中的!Unpin
特征。 - 对于 Unsafe 代码可以强制通过
get_unchecked_mut()
获取&mut T
类型,不过需要保证如下的契约。 - 简单来说,
Pin
用来标识T
不会再被移动,Unpin
意味着允许移动,!Unpin
不允许取消Pin
动作。 - 使用
PhantomPinned
的作用是,一旦结构体被Pin
了,那么就不能Unpin
。
上述说的契约为,对于 Pin<P<T>>
类型:
T
实现了Unpin
,那么P<T>
从被 Pin 到销毁,需要保证P<T>
不被订住。T
实现了!Unpin
,那么P<T>
从被 Pin 到销毁,需要保证P<T>
被订住。
参考
- 对于 Pin 异步编程中的 Pinning 章节介绍,最后有相关的总结。