Would implicit `await` really be a bad idea?

I would even argue that awaiting immediately should be avoided wherever possible, and should not be the normal or default operation. Awaiting futures immediately can a recipe for maximizing latency.

The first futures API that I used was an internal library at Amazon back in 2005. The company had developed many best practices for minimizing latency. One of the rules was to schedule futures as early as possible, and await them as late as possible. This means that typically there is a lot of space between where a future is created and where it is resolved.

For example, we would NOT do this. This would cause the parent task to block on the child task before starting its next chunk of work:

let x = await async_work(); // bad
let y = do_some_stuff();
do_more_stuff(x, y);

And we would NOT do this, which requires the parent task to complete the first chunk of work before scheduling the child task (which will then block the parent).

let y = do_some_stuff();
do_more_stuff(await async_work(), y); // bad

The correct way to write the code is this, which allows the child task and parent task to run concurrently up until the point where the child task's result is needed:

let x = async_work();
let y = do_some_stuff();
do_more_stuff(await x, y); // good

(These optimizations get more complex when a parent task spawns several child tasks, some of which depend on each other, but the principles remain the same. Typically you end up with a bunch of async calls near the start of a function, and bunch of awaits near the end.)

I know these practices are not yet universal, and many programmers do tend to await futures immediately. However, as async programming matures, I think that more people will rediscover these rules. We should not adopt any language design that makes the "good" code above harder to read/write than the "bad" code.

20 Likes