Rust 2018 has shipped, and we’re closing in on the end of the year. While we didn’t manage to ship async/await as part of the edition itself, the community has made quite a lot of progress toward that goal. This post summarizes the state of play, and announces the publication of several crates intended to facilitate use of async/await on the nightly ecosystem.
Why async/await
Before delving into the current status, it’s worth taking a moment to recap the core motivations for async/await, and its special importance for Rust.
Async/await notation is a way of making asynchronous programming more
closely resemble synchronous programming. To see how this works, consider
Read::read
in std::io
:
fn read(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error>
This synchronous method blocks the current thread until data has been read
into buf
, then says how many bytes were read. We can build on this method
to implement read_exact
, a method that continues reading until the buffer
is filled:
fn read_exact<T: Read>(input: &mut T, buf: &mut [u8]) -> Result<(), std::io::Error> {
let mut cursor = 0;
while cursor < buf.len() {
cursor += input.read(&mut buf[cursor..])?;
}
}
In the asynchronous world, we want to perform similar operations, but rather than blocking the current thread we want to leave it free to do other work while the I/O operations complete asychronously. But actually programming directly in that way is incredibly difficult. What we want is to program as if I/O operations will block the current thread, but have the compiler transform this code into more efficient asynchronous execution.
In short, our goal is to be able to write the following:
async fn read_exact<T: AsyncRead>(input: &mut T, buf: &mut [u8]) -> Result<(), std::io::Error> {
let mut cursor = 0;
while cursor < buf.len() {
cursor += await!(input.read(&mut buf[cursor..]))?;
}
}
Comparing the two snippets, there are three changes here:
-
We write
async
beforefn
, to signal that the function should be asynchronously executed on its parent thread. Async functions return their result within aFuture
, representing a value that must be asynchronously computed. -
We use the
AsyncRead
trait (fromfutures::io
), rather than theRead
trait. This makes an asynchronous version of theread
method available (currently provided via theAsyncReadExt
extension trait). -
We enclose the call to
read
withawait!
, signaling that we want to simulate blocking on the operation to complete.
And that’s all.
This approach to asynchrony has proven itself in many other languages already.
But there’s an extra element in the Rust
version: borrowing. For read_exact
, we are able to hold the borrows
of input
and buf
while we use await!
(which may actually clear
the stack and run completely unrelated code). The validity of the
borrowing is still checked, and it works largely similarly to borrowing
within synchronous code. (The main difference: the way elision works in
fn
signatures.)
If you’ve programmed with futures in Rust before, you’ll know this is a
game changer: manual futures code generally must be restricted to 'static
data, which (in addition to its verbosity) takes it far away from
idiomatic Rust, forcing you to program with Arc
and Mutex
far more
frequently than usual.
Below, we’ll check in on the status of various aspects of the transition to this new world.
The book
Part of the work around async/await this year has been writing a new
book, covering the syntax, the underlying Future
API, and ultimately
various programming patterns that emerge. @cramertj has written an early
draft (repo here),
which is already useful for understanding these concepts.
The syntax
The async/await syntax itself has had an implementation on nightly for several months now, and is being used at a fairly large scale in Google’s Fuchsia project. You can find more detail about that usage here.
While there are a few remaining limitations in the implementation of the syntax, the main issue that remains to be resolved prior to any stabilization is the await side of the syntax. @withoutboats recently wrote a blog post describing the issues there in detail.
Today, async
can be used in code blocks, for free functions (async fn
),
and for inherent methods. Ultimately it will be usable in trait method signatures
as well, but this is effectively blocked by existential types, another
feature on track to stabilization relatively soon. In the meantime, there are
several forward-compatible ways to continue using async
blocks within trait
implementations, most simply by placing the async
block into a Box
that
will be removable later, once existential types are stable.
The Supporting APIs
Like many other language features, async/await also requires some support in the
standard library: shipping the Future
trait (and associated machinery) in std
.
That work has been a major thrust this year, and is nearing completion.
Core futures APIs
There’s currently an open RFC proposing stabilization of the futures
APIs, and includes a fairly detailed writeup of the history of
those APIs. The pull request contains a checklist of current blockers; the most
signifcant one at the moment is finalizing the Waker
APIs.
The Pin
API
One of the underlying mechanisms supporting the futures API is the Pin
type,
which is also how we enable borrowing in async
blocks. This API, too, has seen
significant iteration over the course of the year. @withoutboats’s blog post
from a few months ago covers the final design, which has also been
proposed for stabilization. The only remaining sticking point
is around type and trait naming.
Compatibility with futures 0.1
The design of the futures API had to change in breaking ways in order to support async/await. However, there’s a large existing ecosystem of code that uses the earlier futures 0.1 API. Luckily, we’re able to provide a rather ergonomic compatibility layer that makes it possible to move between the two APIs easily, and hence support incremental migration. A recent blog post from @jsdw does an excellent job of laying out how this compatibility story works.
Some new crates
In addition to the compatibility layer the Networking Working Group has also put effort into building crates directly using the new futures API, in order to more fully vet that API, to provide a smoother experience for others wanting to build code using async/await, and to lay out a clear vision of what the new ecosystem might look like.
- Romio, a minimal fork of Tokio based directly on the new futures API. While
Tokio proper aims to provide a comprehensive and opinionated story for the lowest-levels
of async networking code, Romio covers just the essentials: an API surface very similar
to
std::net
, but supporting async/await directly. The crate includes a good bit of documentation and examples, and @withoutboats has written a blog post detailing lessons learned through this port.
- http-service, a tiny crate building on bytes, http, and the new futures API to provide a common interface for http-based services using the new futures API. This crate is partly based on the ongoing work on Tide, where the goal is to seed the ecosystem with numerous small, useful crates of this kind that many different frameworks and libraries can build on. As such, the API is an extraction of the one initially used internally in Tide.
- Tyger (forthcoming), a small crate that builds on top of Hyper to provide a direct http-service interface (and thus usable with async/await directly, without shims). Ultimately Tyger is likely to grow some other higher-level amenities, to complement Hyper’s relatively low-level focus. As with http-service, the crate is an early extraction from the Tide work and is intended to provide a small, community-driven building block that can be used by many other crates. It will be published some time in the next few weeks.
The road ahead
We’ve come a long way toward async/await in 2018! With the futures and pin APIs on the cusp of stabilization, we should very soon be in a position to propose stabilization of async/await proper, hopefully shipping in the first half of 2019. It will be crucial to continue to build out the library ecosystem around these APIs in the coming year. If you want to get involved in any of this exciting work, please drop by the “WG-Net” channels on the Rust Discord!