Async Rust in 2022

Feb. 3, 2022 · Niko Matsakis and Tyler Mandry on behalf of Async Working Group

Almost a year ago, the Async Working Group1 embarked on a collaborative effort to write a shared async vision document. As we enter 2022, we wanted to give an update on the results from that process along with the progress we are making towards realizing that vision.

Writing an async issue aggregator in Rust 2024

To set the scene, imagine it's Rust 2024, and you've decided to build your first project in Rust. You're working on a project that uses GitHub and you'd like a tool that will walk over all the issues on your repository and do some automatic triage. You decide to use async Rust for this. You pull out the Rust book and thumb over to the Async I/O section. In there, it shows you the basic structure of an async Rust application. Like any Rust program, it begins with main, but this time with an async fn...

async fn main() {
    ...
}

Thumbing over to crates.io, you search for "github" and find that there is a nifty crate crabbycat for navigating github issues. You import it and sit down to work. The first thing you need to do is to to iterate over all the issues:

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

Your crate seems to be working well and you happily tweet about it. Soon enough you find yourself with some users and one of them opens a PR to extend it to to support GitLab. To do this, they introduce a trait that allows you to write code that is generic over the issue provider. This trait has one method, issues which returns an iterator (in this case, an async iterator):

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,
}

Now they are able to refactor the main loop into a function that is generic over the IssueProvider. They decide to use a dyn trait to avoid monomorphizing many times.

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:?}");
        }
    }
}

You happily hit merge on the PR and everything works great. Later on, somebody wants to port your system to run on the Chartreuse operating system. Chartreuse is based on an actor model and uses its own custom async runtime -- but luckily for you, you don't care. All your code is seamlessly able to switch the underlying runtime implementation over to the Chartreuse async runtime.

Meanwhile, in 2022...

Of course, the year is still 2022, and the vision we just painted is not reality -- at least not yet. There is a lot of work to do yet in terms of RFCing and implementing the features that will let us write the code we talked about:

  • Writing the IssueProvider trait requires async fns in traits.
  • Taking an &mut dyn IssueProvider argument requires supporting dynamic dispatch in traits that have async functions
    • and returning impl AsyncIterator!
  • The code used a for await? loop, which permitted easy iteration over iterators in async code.
  • The trait for async iteration in the standard library (Stream) has a different name and is not stabilized; its definition is likely to change, too, once we have strong support for async fns in traits.
  • Writing async fn main and changing to an alternate runtime requires portability across runtimes.

As this work proceeds we can expect plenty of changes in the details along the way, and we might decide some pieces aren't worth it; if nothing else, the syntax for generators is a hotly contested topic. What won't change is the the overall vision: that writing async Rust code should be as easy as writing sync code, apart from the occasional async and await keyword.

How we get there

We've organized the Async working group into a number of distinct initiatives, each of which is pursuing one part of the vision. What follows is a list of the currently active groups and some of the things they've done over the last few months since they got up and running.

Async fundamentals initiative

Led by tmandry, currently focused on supporting async fn in traits.

Async iteration initiative

Led by estebank, exploring generators and async generators.

Portability initiative

Led by nrc, exploring what it takes to make code easily portable across runtimes, beginning with standardized traits for things like AsyncRead and AsyncWrite.

Polish initiative

Led by eholk, focused on improving the existing capabilities via smaller changes that collectively make a big difference.

  • We've got a pending PR that will improve the generator's capture analysis when variables are moved before a yield point, as well as another PR that tightens temporary scopes to further avoid unnecessary generator captures.
  • Gus Wynn made significant progress towards a must_not_suspend lint that catches cases where values are live across an await point that should not be.
  • We are starting to look at ways to make async stack traces more readable and helpful.

Tooling initiative

Led by pnkfelix, working to support folks in the async ecosystem who are building interesting tooling to support async Rust others.

  • Michael Woerister is exploring async crashdump recovery, offering a mechanism to recover and inspect the state of an async Rust program based on a crashdump.
  • Eliza Weisman and many others recently announced their 0.1 release of tokio console. Tokio Console is a diagnostics and debugging tool for asynchronous Rust programs. It gives you a live view onto the state of the async runtime, and can also signal warnings when it detects suspicious behavior that might indicate a bug or performance problem.

You can find the complete set of work that we have planned on our roadmap page, which also links to various deliverables we're working toward.

Want to help?

If you're interested in helping out, a good place to start is the How to help section of the polish initiative page. There is also a weekly polish triage meeting which you may want to attend.

  1. We used to be called the Async Foundations Working Group, or wg-async-foundations. wg-async is much easier to type. The focus of the working group being on the "foundations" of async, namely the language and standard library, hasn't changed.