Just to further explore this point, it does seem that .await
being so easy to use sets an unfortunate default for writing asynchronous code, namely serializing execution within your code (obviously, calling code can benefit from asynchrony by setting up multiple asynchronous tasks to await in parallel). If you just use async/await, you aren’t leveraging asynchrony in your own code, you’re only propagating it.
I don’t think the solution is to make propagating asynchrony more difficult, though. Maybe there’s some way leveraging asynchrony could be made just as easy to reach for, or at least nearly?
Strawman: deferred await
One idea would be to allow you to defer awaiting a future so you can set up several asynchronous tasks that can be awaited in parallel. Strawman:
let x = async { qux(async_foo().defer?.baz(), async_bar().defer) };
Basically, this translates to
let x = async {
let a1 = async_foo().map(|v| Ok(v?.baz()));
let a2 = async_bar();
let (b1, b2) = Future::join(a1, a2).await;
qux(b1?, b2)
};
(Throw in a try { }
block if needed to provide a target for ?
if async { }
itself isn’t one.)
Of course, defer
is used with a somewhat different meaning in other languages, I just haven’t come up with a better name. I also deliberately limited this to one expression because it seems like things would start to get weird if you, say, used a variable bound to the result of a deferred computation several statements later without any sort of annotation indicating that defer has poisoned that later statement. If you have to wrap the result of the expression in a future, then using it would force you to either use defer again or use await.