Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ


Choose your language

InfoQ Homepage News Rust Gets Zero-Cost Async/Await Support in Rust 1.39

Rust Gets Zero-Cost Async/Await Support in Rust 1.39

This item in japanese

After getting support for futures in version 1.36, Rust has finally stabilized async/.await in version 1.39. As Rust core team member Niko Matsakis explains, contrary to other languages, async/.await is a zero-cost abstraction in Rust.

Async/await support is implemented in Rust as syntactic sugar around futures, as it happens in most other languages:

An async function, which you can introduce by writing async fn instead of fn, does nothing other than to return a Future when called. This Future is a suspended computation which you can drive to completion by .awaiting it.

Rust is using a slightly different syntax though. This is how you can declare an async function and use it from another function:

async fn a_function() -> u32 { }

async fn another_function() {
    let r : u32 = a_function().await;

As you can see, Rust .await syntax differs somewhat from several other languages where await is implemented as a keyword, including TypeScript, C#, and many others. This choice makes it possible to combine more naturally awaiting an async function completion with the ? operator, which is used for seamless error propagation, such as in a_function().await?.

Most importantly, as Matsakis remarks, async/.await has no runtime cost in Rust. This is due to the fact that calling an async function does not schedule it for execution, as it happens in other languages. Instead, async functions are executed only when you call .await on their future return value. This makes them sort-of "lazy" and allows you to compose a sequence of futures without incurring any penalty.

Another advantage of async/.await is they integrate much better with Rust's borrowing system, which is a big help since reasoning about borrows in async code is usually hard.

The Rust community reacted almost enthusiastically to the introduction of async/.await, which was regarded by many developers as the missing piece to make Rust prime-time-ready.

It was really hard to build asynchronous code until now. You had to clone objects used within futures. You had to chain asynchronous calls together. You had to bend over backwards to support conditional returns. Error messages weren't very explanatory. You had limited access to documentation and tutorials to figure everything out. It was a process of walking over hot coals before becoming productive with asynchronous Rust.

Async/.await support has the potential to greatly improves Rust usability and developer productivity, say others, by simplifying greatly asynchronous programming and making it possible to write firmware/apps in allocation-free, single-threaded environments.

Some developers, though, consider async/.await insufficient and call for higher-level abstractions. One major limit that is highlighted is the need for all functions used in a async/.await call chain to be written with that paradigm in mind, otherwise your program will block at some point. This is a general, not Rust-specific issue; still, its solution would require introducing a runtime system and breaking Rust zero-cost-abstraction philosophy.

An alternative design would have kept await, but supported async not as a function-modifier, but as an expression modifier. Unfortunately, as the async compiler transform is a static property of a function, this would break separate compilation.

As a final remark, the current implementation of async/.await in Rust is only considered a "minimal viable product" and further work will be done to improve and extend it. In particular, this should bring support for using async fn in trait definitions.

Rate this Article