Skip to content
Tech News
← Back to articles

A Design Space Exploration of Async/Await

read original get Rust Atomics and Locks by Mara Bos → more articles
Why This Matters

A new paper shows that async/await, despite looking syntactically similar across languages, behaves inconsistently in practice: a trivial fire-and-forget logging program produces four different outputs across seven modern runtimes, and no two runtimes agree across three variants. That matters because developers routinely port patterns and mental models between languages, and these hidden semantic differences are a source of subtle concurrency bugs.

Key Takeaways
Worth a Look

Rust Atomics and Locks by Mara Bos — If this deep dive into how async/await differs across languages left you curious, Mara Bos's Rust Atomics and Locks digs into the concurrency primitives underneath it all. It's a clear, hands-on guide to threads, futures-adjacent machinery, and memory ordering that pairs perfectly with thinking about straight-line asynchrony.

See Rust Atomics and Locks by Mara Bos on Amazon → Affiliate link — we may earn a commission on purchases, at no extra cost to you. Product picked by AI based on this article; it is not a tested recommendation.

Many programming languages now provide the async / await keywords for expressing concurrency. The design rationale is pretty consistent: to make concurrent programs look more like straight-line code (see: Python, Rust, or Swift). We therefore describe the paradigm which encompasses async/await as straight-line asynchrony, as opposed to using event loops or callbacks.

Language designs for straight-line asynchrony have been brewing for over 15 years. In this project, we wanted to understand: how similar or different is async/await between languages? The short answer is a lot more different than we expected. We wrote a paper, “A Design Space Exploration of Async/Await”, to explain how.

So you think you know async/await? To demonstrate how much modern languages can diverge, here’s a small async program written in pseudocode. One function writes to a log, and another fires off the log write as a background task and moves on. async fn write_to_log () : print ( "A" ) // simulate a slow log write await sleep ( 2 ) print ( "B" ) async fn fire_and_forget () : task = spawn write_to_log () // return without awaiting the task async fn main () : await fire_and_forget () await sleep ( 1 ) print ( "C" ) What would you expect this program to print? Check my answer There isn’t really a right answer, because you were probably right for some language. Below is how seven modern async runtimes actually behave: Show me the outputs Four different answers, for a program whose entire job is to write a log line in the background. And it gets worse. In the paper, we show that across the seven runtimes, no two produce the same output for three variations of this simple program! Do you actually know your language’s async semantics?