Day 26: Pin与Unpin
在学习async/await时,我们接触到了Future。但Future的实现涉及一个重要的概念:Pin。Pin和Unpin是Rust用来处理自引用结构(self-referential structures)的机制,理解它们对于深入掌握async编程至关重要。
2. 什么是自引用结构
Section titled “2. 什么是自引用结构”考虑一个简单的数据结构,其中包含一个字段和该字段的某种”视图”:
struct SelfRef { value: String, // 指针指向value字段 pointer: *const String,}
impl SelfRef { fn new(value: String) -> Self { SelfRef { value, pointer: std::ptr::null(), } }
fn set_pointer(&mut self) { self.pointer = &self.value as *const String; }}这段代码的问题在于:当SelfRef被移动到其他位置时,value的内存地址会变化,但pointer仍然指向旧的地址,导致悬空指针。
3. Unpin标记trait
Section titled “3. Unpin标记trait”Rust用Unpin标记来区分可以在内存中安全移动的类型。大多数类型都是Unpin的:
fn main() { // 大多数类型都实现了Unpin let x: i32 = 5; let y = x; // x被移动,但i32是Unpin,所以可以安全移动 println!("{}", y);
// 手动指定Unpin struct MyStruct(u32); impl Unpin for MyStruct {}}对于Unpin类型,编译器允许我们在持有 &mut Self 时安全地将值从内存的一个位置移动到另一个位置。
4. Pin 的类型系统
Section titled “4. Pin 的类型系统”Pin是一个wrapper,它包装了一个类型T,并限制了对T的可变访问,从而防止T被移动(除非T: Unpin)。
use std::pin::Pin;use std::marker::PhantomPinned;
fn main() { // Pin<&mut T>的行为 let mut x = 5;
// Pin<&mut i32> - i32是Unpin,所以Pin<i32>等价于i32 let pinned: Pin<&mut i32> = Pin::new(&mut x); println!("pinned value: {}", *pinned);
// PhantomPinned是不实现Unpin的类型 struct Test { _pinned: PhantomPinned, }
let mut test = Test { _pinned: PhantomPinned }; // let mut test_pinned = Pin::new(&mut test); // 注释掉的代码无法工作,因为Test不实现Unpin}5. Pin 的区别
Section titled “5. Pin 的区别”Pin有两种主要形式:不可变Pin和Box包装的Pin。
Pin是不可变引用,防止通过这个引用移动值:
use std::pin::Pin;
struct Test { value: String,}
impl Drop for Test { fn drop(&mut self) { println!("Test被释放"); }}
fn take_ownership(_: Test) {}
fn main() { let mut test = Test { value: String::from("hello") };
// 正常情况下,可以将test移动到函数中 // take_ownership(test);
// 但如果我们"pin"了它,就不能再移动 let pinned: Pin<&mut Test> = Pin::new(&mut test);
// pinned允许访问底层数据,但限制了移动 println!("value: {}", pinned.value);
// 无法获取&mut Test然后移动它 // let raw = pinned.get_mut(); // 这不是公开API // take_ownership(raw); // 不可能这样移动}6. pinned future
Section titled “6. pinned future”async/.await生成的Future经常包含自引用结构。考虑:
async fn async_process() { let data = String::from("hello");
// async块中的Future可能需要捕获data的引用 // 但同时data和Future可能被移动 let _future = async { // 这里可能会生成指向data的指针 println!("{}", data.len()); data };}编译器生成的Future可能是自引用的。如果Future被移动,自引用就会失效。Pin通过固定(pinning)Future在内存位置来解决这个问题。
7. Pin::new与Pin::new_unchecked
Section titled “7. Pin::new与Pin::new_unchecked”use std::pin::Pin;
fn main() { // Pin::new要求T: Unpin let mut x = 5; let pinned = Pin::new(&mut x);
// 对于!Unpin类型,需要使用unsafe的Pin::new_unchecked // let pinned = unsafe { Pin::new_unchecked(&mut some_pinned_struct) };}标准库提供了Pin::new_unchecked用于处理!Unpin类型,但这需要unsafe代码。通常我们使用box来创建pin:
use std::pin::Pin;use std::marker::PhantomPinned;
#[tokio::main]async fn main() { // Box::pin创建一个固定在堆上的Future let future = Box::pin(async { println!("这是一个pinned的future"); });
// 可以在await前移动future let moved_future = future; moved_future.await;}8. get_mut和as_mut
Section titled “8. get_mut和as_mut”Pin提供了一些方法来访问底层数据:
use std::pin::Pin;
fn main() { let mut x = 5;
// 创建一个Pin let mut pinned = Pin::new(&mut x);
// get_mut要求T: Unpin if pinned.is_unpin() { let deref = Pin::get_mut(pinned); *deref = 10; }
// as_mut允许获取可变引用进行一些操作 let as_mut = Pin::as_mut(&mut pinned); *as_mut = 20;
println!("x = {}", x);}9. 手动实现Pin处理
Section titled “9. 手动实现Pin处理”如果需要在自己的类型中支持Pin,通常需要实现Future或使用std::pin模块:
use std::pin::Pin;use std::task::{Context, Poll};
struct MyFuture { data: String, index: usize,}
impl std::future::Future for MyFuture { type Output = char;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { // self已经是被Pin包裹的 let this = self.as_mut().get_mut();
if this.index < this.data.len() { let ch = this.data.chars().nth(this.index).unwrap(); this.index += 1; Poll::Ready(ch) } else { Poll::Ready(' ') } }}
fn main() { let future = MyFuture { data: String::from("hello"), index: 0, };
// 使用pin_project或手动处理Pin let pinned = Box::pin(future);
// 驱动Future完成 let rt = tokio::runtime::Runtime::new().unwrap(); let result = rt.block_on(pinned); println!("第一个字符: {}", result);}10. 总结
Section titled “10. 总结”今天我们深入理解了Pin和Unpin机制:
- 自引用结构是包含指向自身字段的指针的数据结构,移动时会导致悬空指针
- Unpin是一个标记trait,实现它的类型可以安全移动
- Pin通过限制对底层数据的访问,防止T被移动
- async/.await生成的Future经常是自引用的,需要Pin来固定
- Pin::new_unchecked和Box::pin是创建固定Future的常用方法
Pin是Rust类型系统处理自引用结构的精妙设计,它使得异步编程在类型安全的前提下成为可能。