A bit off-topic: Is there any theoretical reason you
need async / await syntax at all?
(It's certainly desirable for performance and compatibility to avoid making all subroutines into coroutines, so I understand why most languages have done this.)
And restricting it to the case where coroutines have a single return...
Subroutines are naturally coroutines that don't yield. And it seems like the question of whether it's a subroutine or a coroutine shouldn't be something the programmer needs to worry about.
What I've seen in coroutine libraries is the main reason we need await seems to be for cases when we don't want to await a result.
If you need an unawaited invocation to pass to a routine like gather, you do this:
my_results = await gather(run_job(x) for x in work)
But, even if we can't infer from the type of gather that it must accept a future, a function can have a method that asks it to return a future:
my_results = gather(run_job.future(x) for x in work)
You'd often want to dispense with gather. If I have these statements:
alpha = run_alpha()
beta = run_beta()
gamma = run_gamma()
return alpha + beta + gamma
In most cases, e.g. if these were requests going over the network, we'd prefer to schedule all three at once. If we want to sequence side-effects, then explicit await makes sense:
alpha = run_alpha.await()
beta = run_beta.await()
gamma = run_gamma.await()
return alpha + beta + gamma
That makes the most concurrent option easiest, as opposed to the clunky idiom of "gathering" many results.