To await or not to await?
Should we require you to use .await
? After the epic syntax debates we had, wouldn't it be ironic if we got rid of it altogether, as carllerche has proposed?
Basic idea:
- When you invoke an async function, it could await by default.
- You would write
async foo()
to create an "async expression" -- i.e., to get aimpl Async
.- You might instead write
async || foo()
, i.e., create an async closure.
- You might instead write
Appealing characteristics:
- More analogous to sync code. In sync code, if you want to defer immediately executing something, you make a closure. Same in async code, but it's an async closure.
- Consistency around async-drop. If we adopt an async drop proposal, that implies that there will be "awaits" that occur as you exit a block (or perhaps from the control-flow of a
break
or?
). These will not be signaled with a.await
. So you can no longer rely on every await point being visible with a keyword. - No confusion around remembering to await. Right now the compiler has to go to some lengths to offer you messages suggesting you insert
.await
. It'd be nice if you just didn't have to remember. - Room for optimization. When you first invoke an async function, it can immediately start executing; it only needs to create a future in the event that it suspends. This may also make closures somewhat smaller.
- This could be partially achieved by adding an optional method on the trait that compiles a version of the fn meant to be used when it is immediately awaited.
But there are some downsides:
- Churn. Introducing a new future trait is largely invisible to users except in that it manifests as version mismatches. Removing the await keyword is a much more visible change.
- Await points are less visible. There may be opportunity to introduce concurrency and so forth that is harder to spot when reading the code, particularly outside of an IDE. (In Kotlin, which adopts this model, suspend points are visible in the "gutter" of the editor, but this is not visible when reviewing patches on github.)
- Await points today also indicate where a live
Send
orSync
value will affect if the future is send or sync (but with async-drop, this would no longer be true).
- Await points today also indicate where a live
- Async becomes an effect. In today's Rust, an "async function" desugars into a traditional function that returns a future. This function is called like any other, and hence it can implement the
Fn
traits and so forth. In this "await-less" Rust, an async function is called differently from other functions, because it induces an await. This means that we need to considerasync
as a kind of "effect" (likeunsafe
) in a way that is not today.- Similarly, how do we handle the case of
fn foo() -> impl Future
? Does that auto-await, or does it require an explicitawait
keyword? - What happens when you invoke an
async fn
in a sync environment?
- Similarly, how do we handle the case of
Frequently asked questions
How could you do this anyway? Wouldn't it be a massive breaking change?
It would have to take place over an edition.