异步工作组 2023 年的首要目标是稳定化特性中异步函数(async functions in traits)的“最小可行产品”(MVP)版本。我们目前的目标是在 Rust 1.74 版本中完成稳定化。本文阐述了我们计划发布的特性以及各项特性的当前状态。
去年 11 月,我们发文介绍了 trait 中 async fn 的 nightly 支持,并确定了一些关键的后续步骤,其中最重要的是支持 Send 约束,以便在泛型函数中创建任务。自那时以来,我们进行了大量的设计探索,并收集了一系列案例研究,评估当前代码在实践中的表现如何。
截至目前,本文中描述的所有功能都已在 nightly 编译器中可用。其中一些工作是使用实验性功能门控(experimental feature gates)实现的,以便我们能够进行案例研究并验证其可行性;我们目前正在为这些功能撰写 RFC(详细信息见下文)。
MVP 第一部分:“特性中异步函数”的核心支持
解释我们即将稳定化的内容的最简单方法是使用代码示例。首先,我们将允许在 trait 定义中使用 async fn
...
...然后你可以在相应的 impl 中使用 async fn
带有异步函数的 trait 随后就可以像往常一样使用
async
状态: 此功能已在 RFC 3185 中描述,于 2021 年 12 月 7 日合并,并在 nightly 中可用。它在我们之前的博文中已详细介绍。
MVP 第二部分:Send 约束和关联返回类型
在使用 trait 中的异步函数时会出现一个同步函数不会遇到的复杂情况。许多异步运行时(特别是包括 Tokio 和 async-std 的默认配置)使用了工作窃取线程调度器。这意味着 Future 可能会在工作线程之间动态移动以实现负载均衡。因此,Future 只能捕获 Send
数据。
然而,如果你编写一个在这些运行时之一上创建任务的泛型异步函数,你就会开始遇到编译错误(Playground)
async
问题在于 hc.check()
返回的 future 不能保证是 Send
的。它可能访问非 Send 的数据。解决方案是添加一个 Send
约束,但考虑到这是一个异步函数,如何做到这一点并不直观。我们如何讨论调用 hc.check()
返回的 future?关联返回类型(Associated return types)提供了答案。我们可以将上述函数转换为使用显式的类型参数 HC
(而不是 impl HealthCheck
),然后添加一个新的约束:HC::check(): Send
。这意味着“HC::check
返回的值必须是 Send
类型”
async
当然,为了使用这种写法,我们不得不将接受 impl HealthCheck
重写为显式的 HC
类型参数,这有点不幸。RFC 2289,“关联类型约束”(associated type bounds),引入了一种紧凑的写法来解决这个问题。该 RFC 不是本次 MVP 的一部分,但如果它稳定化了,那么就可以简单地写成
async
在我们之前的博文中,我们推测这个问题在实践中可能不会经常出现。然而,我们的案例研究发现它出现的频率相当高,因此我们认为需要一个解决方案。我们探索了多种解决方案,并得出结论:关联返回类型(ARTs)是一个灵活且相当符合人体工程学的构建模块,这使得它们非常适合作为 MVP 的一部分。
状态: 关联返回类型(Associated return types)有一个实验性实现,我们目前正在起草 RFC。存在几个需要修复的未解决 bug。我们还发现,在包含许多方法的 trait 中,ART 会变得冗长,将来可能会考虑更简洁的语法(见下文)。
MVP 第三部分:“特性中的 impl trait”(返回位置)
在 Rust 中,异步函数是返回 impl Future
的函数的“语法糖”,trait 中的异步函数也不例外。作为 MVP 的一部分,我们计划稳定化在 trait 定义和 trait impl 中使用 -> impl Trait
的语法。
特性中的 Impl trait 有各种用途,但对于异步编程来说,一个常见的用途是通过执行一些同步工作来避免捕获函数的所有参数,然后返回一个 future 来处理其余部分。例如,这个 LaunchService
trait 声明了一个不捕获 self
的 launch
函数(类似于现有的 Tower Service
trait)
由于 async fn
是返回 impl Future
的常规函数的语法糖,这两种语法形式可以互换使用。
尽管在异步编程中经常出现“特性中的 impl trait”的需求,但它们是一项通用功能,在许多与异步无关的场景中也很有用(例如,从 trait 方法返回迭代器)。
状态: 返回位置的特性中 impl trait 有一个实验性实现,并在目前开放的 RFC 3425 中有所描述。此功能本身是独立的,但它是特性中 async fn
整体图景的重要组成部分。
评估 MVP
为了评估此 MVP 的实用性,工作组收集了五个案例研究,涵盖了AWS SDK 中使用的 builder-provider 模式;以及 async function 在 tower 中的潜在使用、在 embassy 中的实际使用、Fuchsia 网络栈以及微软的一个内部工具。这些研究验证了上述功能足以在 trait 中使用 async function 来实现各种用途,尽管某些情况下需要变通方法(因此才有了“MVP”的名称)。
MVP 不支持或支持不好的方面
案例研究揭示了 MVP 支持得不太好的两种情况,但这两种情况都有可用的变通方法。这些变通方法是机械性的,一旦 MVP 在 stable 版本中可用,就可以通过自定义 derive 或 crates.io 上的其他 crate 来自动化实现它们。
模拟动态分发
在 MVP 中,使用异步函数的 trait 不是“dyn safe”的,这意味着它们不支持动态分发。因此,例如,对于我们之前看到的 HealthCheck
trait,你不能写成 Box<dyn HealthCheck>
。
起初,这似乎是一个关键的限制,因为许多用例都需要动态分发!但事实证明,存在一种变通方法。你可以在 crate 内部定义一个“擦除”的 trait,从而实现动态分发。这个过程由 erased serde 等 crate 首创,并在builder-provider 案例研究中详细解释。
为了在短期内使这种变通方法更容易实现,我们计划提供一个过程宏来自动化它。将来,async fn 应该能够直接与 dyn Trait
一起工作。
Send 约束冗长,特别是对于包含许多方法的 trait
关联返回类型提案对于只有一个方法的 trait 非常有效,但对于包含许多方法的 trait 可能会很麻烦。一个便捷的解决方案是使用“trait 别名模式”:1
使用这样的模式意味着你可以写成 T: SendHealthCheck
。我们计划提供一个过程宏来为你生成这些 trait 别名,这样你就可以写出类似下面这样的代码
将来,trait transformer 之类的东西可能会在没有过程宏的情况下提供更简洁的语法。但是,由于存在需要关联返回类型提供的细粒度控制的用例,我们选择先稳定化关联返回类型,并在积累经验后再考虑更简洁的语法。
时间表和路线图
我们的目标是在 Rust 1.74 版本中稳定化 MVP,该版本将于 2023 年 11 月 16 日发布。此特性的分支窗口(branch window)于 7 月 14 日开放,于 8 月 24 日关闭。为了真正实现在 1.74 版本中稳定化,我们希望在发布分支创建之前预留修复可能出现的 bug 的时间。此目标的关键里程碑如下:
- MVP 实现
- 案例研究评估完成
- 接受返回位置 impl trait 的 RFC(目标:2023-05-31)
- 接受关联返回类型的 RFC(目标:2023-06-15)
- 评估期和 bug 修复(目标:2023-06-30)
- 稳定化报告撰写完成(目标:2023-07-01)
- 1.74.0 版本稳定化完成(目标:2023-07-21)
你可以在我们的 GitHub 项目中找到完整的时间表。
下一步是什么?
那么,一旦这个 MVP 完成,下一步是什么?我们接下来的即时目标是在 2024 年推出动态分发和异步闭包支持。这些加在一起将为解决未来的异步问题(例如 async drop 支持、简单的异步迭代器或跨运行时可移植性)奠定坚实的基础。