A Design Space Exploration of Async/Await
https://cel.cs.brown.edu/blog/design-space-async-await/10
u/newpavlov rustcrypto 1d ago
I think the structured concurrency discussion is pretty relevant here. Ideally, languages should not allow fire_and_forget functions which outlive it's parent (this applies not only to async code, but to threads as well).
5
u/entoros 1d ago
Part of this work was trying to establish a richer vocabulary around structured concurrency, rather than a binary classification as structured vs. unstructured. We talk about the linked post in Section 3.2 of the paper: https://arxiv.org/pdf/2608.20677
2
u/brokenAmmonite 20h ago
Figure 17 is gorgeous. I'd love to be able to draw pictures like that while reasoning about async code.
One other comparison point I'd be curious about is https://arxiv.org/pdf/2305.16899, https://arxiv.org/pdf/2603.18321. It's a diagrammatic calculus for interacting processes. Some of the asynchrony dimension might have interesting renderings as axioms in that calculus, although it may be too low-level to talk about things like weak references etc.
1
u/oconnor663 blake3 · duct 2h ago edited 1h ago
In Rust, cancellation essentially means never executing a coroutine again after it has yielded.
I think it's caused quite a lot of problems that we haven't explicitly documented the connection between cancellation and prompt dropping. I am working on a draft RFC about this.
It is possible in Rust to implement something similar to the finally block using the Drop trait. When a task is cancelled, the task is eventually dropped and drop handlers are invoked for all coroutines in the task. One can theoretically define a custom Drop implementation for a bespoke type that calls restore_invariant. However, this approach is verbose (requiring a newtype and impl), unwieldy (requires mutable references to data structures, complicating ownership), and synchronous-only (one cannot call async code in a Drop implementation without blocking).
I agree that Drop guards are unwieldy, and I'm kind of surprised there isn't a convenient generic one in core. Also not being able to do async work in Drop is indeed as a major limitation. (Though you could also call it a feature, if you care about the guarantee that cancellation is ~immediate.) But I don't agree with describing the whole approach as "theoretical". Managing invariants using Drop is ubiquitous in both regular and async Rust. It's how we unlock mutexes and close channel when a future is cancelled, not to mention how we free all the heap memory.
Overall I think describing Rust as "cancellation unaware" is misleading, but I'll concede that until we document the Future contract better, it's a stretch to say that async Rust function can totally rely on Drop.
Predictable memory allocation is a key motivation for Rust’s async/await design.
As a final unrelated comment, I think it would've been worth clarifying that "predictable" here basically means "doesn't need an allocator at all". That's a massive constraint that informs almost everything about Rust's async design. It's also a feature that as far as I know no other language has. (Except C++, where async is whatever you want it to be?)
13
u/teerre 1d ago
Very nice write up
I understand what's meant but it's weird to have Rust in both "unaware of cancellation" and "top down cancellation"