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 writingasync fn
instead of fn, does nothing other than to return a Future when called. ThisFuture
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.