跳到主要内容

Future trait 与 Pin

问题

Future trait 是如何定义的?为什么需要 Pin

答案

Future trait

pub trait Future {
type Output;

// 运行时调用 poll 驱动 Future 执行
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

pub enum Poll<T> {
Ready(T), // 完成,返回结果
Pending, // 未完成,稍后再 poll
}

运行时(如 Tokio)不断调用 poll(),直到返回 Ready。Future 在 Pending 时通过 Waker(在 Context 中)通知运行时"我准备好了,可以再次 poll 了"。

为什么需要 Pin

async fn 编译为状态机,可能包含自引用

async fn example() {
let data = vec![1, 2, 3];
let reference = &data; // 引用 data
some_async_op().await; // ← 在这里暂停
println!("{:?}", reference); // 恢复后使用引用
}

// 编译器生成的状态机(伪代码):
struct ExampleFuture {
data: Vec<i32>, // 数据
reference: *const Vec<i32>, // 指向 data 的指针(自引用!)
state: State,
}

如果这个状态机被移动(move),reference 指针就会变成悬垂指针。Pin 防止这件事发生:

// Pin<&mut T> 保证 T 不会被移动
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>;

Pin 与 Unpin

// Unpin:可以安全移动,Pin 对它没有限制
// 大多数类型自动实现 Unpin
struct Simple { x: i32 } // 自动 Unpin

// !Unpin:不能移动(用 PhantomPinned 标记)
use std::marker::PhantomPinned;
struct SelfRef {
data: String,
ptr: *const String,
_pin: PhantomPinned, // 标记为 !Unpin
}
类型Unpin说明
i32, String, Vec<T>没有自引用
async fn 生成的 Future可能有自引用
手动标记 PhantomPinned显式声明

实际使用中的 Pin

大多数开发者不需要直接操作 Pin——async/await 和运行时会正确处理。但在以下场景需要理解:

use std::pin::Pin;
use std::future::Future;

// 返回 Pin<Box<dyn Future>>(动态分发的 Future)
fn dynamic_future() -> Pin<Box<dyn Future<Output = i32>>> {
Box::pin(async { 42 })
}

// 手动实现 Future
struct Delay {
when: std::time::Instant,
}

impl Future for Delay {
type Output = ();

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if std::time::Instant::now() >= self.when {
Poll::Ready(())
} else {
// 注册 waker,到时间后唤醒
let waker = cx.waker().clone();
let when = self.when;
std::thread::spawn(move || {
std::thread::sleep(when - std::time::Instant::now());
waker.wake();
});
Poll::Pending
}
}
}

常见面试问题

Q1: Future 是懒惰的还是即时的?

答案

懒惰的。创建 Future 不执行任何操作,必须被 poll() 驱动(通常通过 .awaittokio::spawn)。这与 JavaScript 的 Promise(创建即执行)完全不同。

Q2: Box::pin 什么时候用?

答案

当需要返回 dyn Future(Trait Object)时,因为大小不确定,必须放在堆上并 Pin:

// 返回不同的 Future 类型
fn choose(flag: bool) -> Pin<Box<dyn Future<Output = i32>>> {
if flag {
Box::pin(async { 1 })
} else {
Box::pin(async { 2 })
}
}

Q3: 什么是 Waker?

答案

Waker 是 Future 通知运行时"我准备好了"的机制。当 Future 返回 Pending 时,它应该克隆 Waker 并在条件满足时调用 waker.wake()。运行时收到通知后重新 poll() 这个 Future。

Q4: async 代码中的递归如何处理?

答案

async fn 不支持直接递归(因为状态机大小无限)。需要用 Box::pin 包装:

async fn factorial(n: u64) -> u64 {
if n <= 1 { return 1; }
let sub = Box::pin(factorial(n - 1)).await;
n * sub
}

相关链接