2022 年的异步 Rust

2022 年 2 月 3 日 · Niko Matsakis 和 Tyler Mandry 代表 异步工作组

大约一年前,异步工作组1 开始了一项合作努力,编写一份 共享的异步愿景文档。在进入 2022 年之际,我们想更新一下该过程的结果,以及我们在实现该愿景方面取得的进展。

在 2024 年用 Rust 编写一个异步问题聚合器

为了说明情况,假设现在是 2024 年,你决定用 Rust 构建你的第一个项目。你正在开发一个使用 GitHub 的项目,并且你想要一个工具来遍历你存储库中的所有问题并进行一些自动分类。你决定为此使用异步 Rust。你拿出 Rust 书籍,翻到异步 I/O 部分。在那里,它向你展示了一个异步 Rust 应用程序的基本结构。像任何 Rust 程序一样,它以 main 开头,但这次是 async fn...

async fn main() {
    ...
}

翻到 crates.io,你搜索 “github”,发现有一个很棒的 crate crabbycat 用于浏览 github 问题。你导入它并坐下来开始工作。你需要做的第一件事是遍历所有问题

async fn main() {
    for await? issue in crabbycat::issues("https://github.com/rust-lang/rust") {
        if meets_criteria(&issue) {
            println!("{issue:?}");
        }
    }
}

你的 crate 似乎运行良好,你很高兴地在推特上发布了它。很快,你发现自己有了一些用户,其中一个用户打开了一个 PR,将其扩展为支持 GitLab。为此,他们引入了一个 trait,允许你编写可用于问题提供程序的通用代码。此 trait 有一个方法,issues,它返回一个迭代器(在这种情况下,是一个异步迭代器)

trait IssueProvider {
    async fn issues(&mut self, url: &str)
        -> impl AsyncIterator<Item = Result<Issue, Err>>;
}

#[derive(Debug)]
struct Issue {
    number: usize,
    header: String,
    assignee: String,
}

现在,他们可以将主循环重构为一个通用于 IssueProvider 的函数。他们决定使用 dyn trait 以避免多次单态化。

fn process_issues(provider: &mut dyn IssueProvider) {
    for await? issue in provider.issues("https://github.com/rust-lang/rust") {
        if meets_criteria(&issue) {
            println!("{issue:?}");
        }
    }
}

你很高兴地合并了 PR,一切都运行良好。稍后,有人希望将你的系统移植到 Chartreuse 操作系统上运行。Chartreuse 基于 Actor 模型,并使用其自己的自定义异步运行时 —— 但幸运的是,你并不关心。你的所有代码都能够无缝地将底层运行时实现切换到 Chartreuse 异步运行时。

与此同时,在 2022 年...

当然,现在还是 2022 年,我们刚刚描绘的愿景还不是现实 —— 至少目前还不是。在 RFC 和实现允许我们编写我们谈论的代码的功能方面,还有很多工作要做

  • 编写 IssueProvider trait 需要 trait 中的 async fn。
  • 获取 &mut dyn IssueProvider 参数需要在具有异步函数的 trait 中支持动态分发
    • 并返回 impl AsyncIterator
  • 该代码使用了一个 for await? 循环,允许在异步代码中轻松地遍历迭代器。
  • 标准库中异步迭代的 trait (Stream) 有不同的名称,并且没有稳定;一旦我们对 trait 中的异步 fn 有了强大的支持,它的定义也可能会发生变化。
  • 编写 async fn main 并更改为备用运行时需要在运行时之间实现可移植性。

随着这项工作的进行,我们可以预期细节会有很多变化,我们可能会认为某些部分不值得做;如果说没有别的原因,生成器的语法也是一个备受争议的话题。不会改变的是总体愿景:编写异步 Rust 代码应该像编写同步代码一样容易,除了偶尔出现的 asyncawait 关键字。

我们如何实现它

我们已将异步工作组组织成若干不同的计划,每个计划都致力于实现愿景的一部分。以下是当前活跃的组的列表,以及自它们启动以来的过去几个月中它们所做的一些事情。

异步基础计划

tmandry 领导,目前专注于支持 trait 中的 async fn

异步迭代计划

estebank 领导,探索生成器和异步生成器。

可移植性计划

nrc 领导,探索如何使代码在运行时之间轻松移植,从 AsyncReadAsyncWrite 等标准化 trait 开始。

优化计划

eholk 领导,专注于通过较小的更改来改进现有功能,这些更改共同产生了巨大的影响。

  • 我们有一个 待处理的 PR,当变量在 yield 点之前被移动时,它将改进生成器的捕获分析,以及 另一个 PR,它收紧了临时作用域以进一步避免不必要的生成器捕获。
  • Gus Wynnmust_not_suspend lint 方面取得了重大进展,该 lint 可以捕获那些在 await 点之间应该不活动的 live 值。
  • 我们正在开始研究如何使 异步堆栈跟踪 更具可读性和帮助性。

工具计划

pnkfelix 领导,致力于支持异步生态系统中构建有趣工具以支持其他异步 Rust 用户的用户。

  • Michael Woerister 正在探索 异步崩溃转储恢复,提供一种基于崩溃转储恢复和检查异步 Rust 程序状态的机制。
  • Eliza Weisman 和 许多其他人 最近 宣布发布 0.1 版本tokio console。Tokio Console 是一个用于异步 Rust 程序的诊断和调试工具。它为您提供了异步运行时状态的实时视图,并且当它检测到可能表明错误或性能问题的可疑行为时,它也可以发出警告。

您可以在我们的 路线图页面 上找到我们计划的完整工作集,该页面还链接到我们正在努力实现的各种交付成果。

想帮忙?

如果您有兴趣提供帮助,一个好的起点是优化计划页面的 如何提供帮助 部分。还有一个每周的 优化分类会议,您可能想参加。

  1. 我们以前被称为异步基础工作组,或 wg-async-foundations。wg-async 更容易输入。工作组的 重点 是异步的 “基础”,即语言和标准库,这一点没有改变。