一句话总结
Future 是"一个尚未完成的异步计算"在 Rust 类型系统里的样子.
运行时通过反复调用它的 poll 方法来推进:返回 Pending 表示"还没好,我让出",返回 Ready(值) 表示"完成了,这是结果".
Future 是惰性的--不被 poll 就什么都不做.
承接上一篇的问题
上一篇结尾留下一个问题:Go 把"挂起/唤醒任务"做成了运行时的隐藏行为,而 Rust 没有内建运行时,必须用语言层的抽象来表达"一个可以暂停,之后恢复的任务".这个抽象,就是 Future.
Future 字面意思是"未来"--它代表一个现在还没算出,但未来会有结果的值.
类比点外卖后拿到的订单号:订单号本身不是饭,但它是"饭终将到来"的凭证,可以拿着它去查进度.Future 就是异步结果的"订单号".
Future trait 的定义
剥掉细节,Future 的核心定义只有一个方法:
|
暂时忽略 Pin 和 Context(后面会说),先抓住主干:有一个 poll 方法,它返回 Poll.而 Poll 是个只有两种变体的枚举:
|
运行时驱动一个 Future 的方式,就是不断调用 poll.可以把它读成一句问话:"完成了吗?"
答 Ready(v) 就是"好了,给出结果 v";答 Pending 就是"还没,先忙别的去吧".
轮询模型:谁来 poll,何时 poll
一个自然的担心:难道运行时要像忙轮询那样,对着一个 Pending 的 Future 疯狂重试 poll 吗?不会. 这正是 Rust 异步设计的精妙处.
当 Future 返回 Pending 时,它有一个义务:在让出之前,先把 Context 里携带的 waker(唤醒器)登记到它正在等待的事件源上(比如登记给 epoll).这样:
|
所以是"就绪了再唤醒,唤醒了才 poll",不是空转重试.waker 就是连接"事件循环就绪通知"和"重新 poll 这个任务"的那根线.
poll 返回 Pending 时登记 waker,就像打客服电话遇到占线,系统说"留个号码,有空位回拨",而不是让我们一直按重拨.
waker 就是留下的回拨号码.
关键性质:Future 是惰性的
这是 Go 程序员最容易栽跟头的一点.在 Go 里,go f() 一执行,goroutine 立刻开始在后台跑.但在 Rust 里,创建一个 Future 什么都不会发生--它只是个"待办描述",必须有运行时去 poll 它,里面的代码才会执行.
|
一个 Future 若没有被 .await,也没有 spawn 给运行时,它就是一段死代码.编译器通常会给出 unused must_use 警告--看到它别忽略.
记住口诀:Future 不被 poll,等于没写.
Go 的 goroutine 是"点火即发射"(eager):go f() 立刻起跑.
Rust 的 Future 是"蓝图待施工"(lazy):画好图纸(创建 Future),但不递交给施工队(运行时)去 poll,工地上不会动一砖一瓦.
这个差异会贯穿异步 Rust 的全过程.
Future 的本质是状态机
一个能"暂停又恢复"的任务,凭什么记得"上次跑到哪了"?答案是:每个 Future 本质上是一个状态机(state machine).它把"从头到尾的执行过程"切成若干段,段与段之间就是可能让出的暂停点.
设想一个任务:先读文件,再发网络请求,最后返回.它对应的状态机大致是:
|
每次 poll,状态机从当前状态尽量往前推进,推到一个还没就绪的暂停点就返回 Pending;下次被 poll 时,从上次停下的状态接着跑.当前状态,局部变量等"现场",都保存在这个 Future 结构体里.
几乎不用手写状态机
好消息:这个状态机不需要手动实现.下一篇会看到,async/await 语法让编译器自动把代码翻译成这样的状态机.
本篇理解"Future = 可暂停的状态机,靠 poll 推进,靠 waker 唤醒"即可--这是看懂 async/await 的地基.
关于 Pin 和 Context(先建立印象)
回到 poll 签名里那两个被跳过的参数,这里给个够用的直觉,不展开:
| 参数 | 作用直觉 |
|---|---|
Context |
装着 waker 的盒子.Future 要让出时,从这里取出 waker 登记给事件源,以便日后被唤醒. |
Pin<&mut Self> |
保证这个 Future 在内存里"钉住不动".因为状态机内部可能有指向自身的引用(自引用结构),移动会让指针失效,Pin 防止这种移动. |
入门阶段几乎不会直接和 Pin 打交道--它由 async/await 和运行时在幕后处理.知道"它为自引用状态机保驾护航"就足够了.
快速回顾
- Future:代表一个未完成的异步计算,核心是
poll方法,产出类型由关联类型Output指定. - Poll 枚举:
Ready(v)表示完成并给出结果,Pending表示未就绪,稍后再 poll. - 不空转:返回 Pending 前先登记
waker,事件就绪时由 waker 触发"重新 poll",而非忙轮询. - 惰性:创建 Future 不执行任何代码;不被 poll(未 await / 未 spawn)等于死代码--与 Go 的 eager goroutine 相反.
- 状态机:Future 本质是保存执行现场的状态机,在暂停点之间往返推进;下一篇由编译器自动生成.
- Pin / Context:Context 装 waker;Pin 钉住自引用状态机防移动,入门期由框架代管.
动手练习
- 解释 Pending 义务:用自己的话解释
poll返回Pending时,Future 必须先做什么,为什么. - 观察警告:写一段创建了 Future 却不 await 的代码,观察编译器的
unused must_use警告. - lazy vs eager:把"创建 Future"和 Go 的
go f()对比,说明 lazy 与 eager 的本质差异. - 画状态机:给一个"先 sleep 1 秒,再返回 42"的任务画出它的状态机(状态,暂停点,转换).
- 查阅文档:查阅标准库
std::future::Future文档,确认poll的真实签名与Poll的两个变体.