Rust 异步的四年计划
Rust 中的 async/await 仍然高度两极分化:一些开发者赞赏它能支撑高并发、高效率的网络服务,而另一些人则觉得它在易用性上别扭、在代码库中具有“传染性”,并且不太适合嵌入式或更简单的场景。评论者围绕一份 Rust async 的四年路线图展开讨论,争辩的焦点包括 trait 中的 async、生成器、标准库里的 `block_on`、与运行时无关的 trait,以及 std 是否应提供默认执行器。更深层的问题则是 Rust 作为系统编程语言的根基,与它在 Web 和服务端后端中的日益广泛应用之间的张力,以及对重大语言特性稳定化速度过于保守、过于缓慢的挫败感。
异步“传染性”和替代方案
- 许多人抱怨:如果一个依赖是 async,那么“所有东西都必须是 async”。
- 也有人反驳说,可以通过以下方式隔离 async:
- 启动一个小型运行时(例如 Tokio 单线程、smol、pollster),并使用
block_on。 - 在专用线程上运行异步代码,并通过通道通信。
- 启动一个小型运行时(例如 Tokio 单线程、smol、pollster),并使用
- 批评者回应说,这仍然会引入沉重的运行时和额外依赖,而这会影响构建时间、信任以及受限环境。
易用性、复杂性与现实世界经验
- 有些人认为 Rust 异步是一个重大瑕疵:具有传播性、直觉性差、生命周期负担重、编译器错误信息差、闭包难写,以及
async_trait的交互别扭。 - 另一些人则表示,他们在大型生产代码库中多年使用都很顺畅,认为这些抱怨被夸大了,而且往往来自有限或过时的经验。
- 对于反复出现的“async 很难/很差”争论,人们也感到疲惫,因为这些争论忽视了设计细节或所链接文章中的细微差别。
领域差异:服务器 vs 嵌入式和系统
- 对于高并发网络服务(HTTP 服务器、DB 密集型代理),许多人认为 async 对于可预测的性能和可扩展性是不可或缺的。
- 对于嵌入式和底层系统,则存在分歧:
- 有些人完全回避 async(更偏好 RTOS、actor、线程、中断、DMA、事件循环)。
- 另一些人则称赞像 Embassy 这样的框架,认为它们是轻量级 RTOS 替代方案,并且任务结构更易用。
运行时、Tokio 的主导地位,以及 stdlib
- Tokio 实际上已经“赢得”了这个生态;许多 crate 强依赖它(锁、通道、spawn)。
- 有些人认为这会带来文化和架构问题,并希望在
std中提供与运行时无关的 trait(AsyncRead/Write、Stream、spawn、locks、channels)。 - 提案包括:
- 在
std中提供一个最小执行器或block_on(可能类似 pollster)。 - 不要让 Tokio 成为默认运行时;据称 Rust 和 Tokio 的维护者都反对这一点。
- 在
并发模型:线程、async、绿色线程
- 人们争论 async 应该是语言特性,还是库/工具问题。
- 有人认为对大多数负载来说,每连接一个线程已经足够好且更简单;async 主要帮助处理大量并发的 IO 绑定任务。
- 也有人认为绿色线程/纤程(Go 风格的 goroutine、Java Loom)相比有色 async 能提供更好的开发体验,而且不会污染 API。
- 反对意见是:Rust 之前已经移除了绿色线程;在 Rust 的安全模型和可嵌入性约束下把它们重新引入并不简单。
语言设计缺口与计划
- 提到的痛点包括:Pin/Unpin 的复杂性、缺少 async 生成器/迭代器、trait 中 async 的故事薄弱(虽然即将改善)、async 闭包的易用性不足、没有 async drop、缺少线性/不可忘记类型。
- 有些人希望引入
Movetrait 和默认不可移动语义来简化 async;另一些人则持谨慎态度。 - 生成器被视为与 async 密切相关且很有价值,但其现实收益仍存在争议。
Rust 的演进速度与治理
- 有些人批评稳定化“慢如龟爬”(例如
std中的block_on、生成器、更深层的 async 修复),希望时间线是 6 个月而不是多年计划。 - 也有人为缓慢、保守的稳定化辩护,因为这些特性实际上会长期存在,必须在冻结前做到正确。