BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

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

Bookmarks

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

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

  • lazy, not zero-cost

    by Constantine Plotnikov,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    This is actually lazy evaluation rather than zero-cost from the text. Less paid at point of the call, more at the point when actual work is done. It is possible design with own advantages, but not w/o disadavantages:


    • Higher memory and computation cost (lazy is not free)

    • Less certainty about execution time or context and life-time of data and resources passed to call (for non-gc language this should be more interesting question)

    • Additional risks that code will be never executed due to unrelated failure (important for cleanup code)


    Generally async is more difficult to reason about than sync code, anything that makes it more difficult to reason about does not look good to me.

  • Re: lazy, not zero-cost

    by Sergio De Simone,

    Your message is awaiting moderation. Thank you for participating in the discussion.

    Hi Constantine,

    thanks for your comments.

    As a minor remark, I think the statement about "zero-cost abstraction" is correct when comparing async/.await to futures, meaning async fn does not add any additional cost on top of future's. The post clarifies this resorts to a lazy behaviour, in the end.

    As to the rest, yours are surely all great points and Rust developers should keep them in mind to avoid surprises.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT