One thing we’ve left as an unresolved question so far in the matter of async/await syntax is the exact final syntax for the await operation. In the current implementation, awaits are written using a compiler plugin:

async fn foo() {
    await!(bar());
}

This is not because of any technical limitation: the reason we have done this is that we have not decided on the precise, final syntax for the await operation. However, no one really wants to stabilize on the current syntax, so some forward progress in this area would be beneficial for us.

I’m proposing in this post that in the near future (😏) we introduce await as a keyword with mandatory delimiters (similar to unsafe) as a more lasting syntax which is forward compatible with other alternatives as well.

The problem of precedence

The reason we currently use the compiler plugin syntax is that await and ? have a tricky problem involving operator precendence. Consider this code:

let response = await http::get(url)?;

The return type of the http::get function is almost certainly something with the same shape as impl Future<Output = io::Result<Response>>. That is, the function returns a future that will eventually either succeed or return an error. What this means is that what you want the behavior to be is this:

  1. First, await the future, evaluating it to its output (io::Result<Response>).
  2. Second, use ? to unwrap the result, early returning with the error if it wasn’t successful.

What this means is that you want await to bind tighter than ? - you want the ? operatior to be applied to await http::get(url), not the other way around. But when you look at that code, it would be fair to assume that things work the other way around - ? is almost always written without a preceding space, which certainly suggests that it binds tighter than await.

And this is a representative example. Any future that operates over IO (which is the main reason async/await exists) will probably return some kind of error, and its fairly rare to return a result while setting up a future, before it begins evaluating. What this means is that there is a conflict between the useful order of precedence and the obvious one.

Potential solutions to precedence problems

There are several possible ways to solve this problem, and we settled on the await!() syntax to avoid having to pick one:

  1. Pick the useful precedence. If users want the uncommon order of operations, they would write: await (function()?), and the default precedence might surprise someone reading asynchronous code for the first time (though they would hopefully pick it up quickly since it is probably commonly used).
  2. Pick the obvious precedence. Whenever users want to await and then ?, they will have to use parens: (await function())?. This keeps the code from ever surprising anyone, but at a pretty steep cost: users will be writing this weird parens code all the time.
  3. Pick the obvious precendence, but introduce a new syntax sugar to avoid the weirdly parenthesized code. For example, await? function(): by putting the ? on await, that is a special combined await-then-? operation.
  4. Introduce some kind of postfix syntax instead of prefix await. A postfix syntax would never have any ambiguity about precedence. The problem is that such a syntax would be very exotic and no one has come up with a proposal for a particular syntax that was widely popular.
  5. Always require delimeters on await. This way it will always be clear that what you are throwing, because a ? inside the braces throws before the await and a ? outside the braces throws after.

The delimiter solution is forward compatible

Shipping async/await is high priority, so we need to find a pragmatic solution in the near future. This led me to ask the question: are any of these possible syntaxes forward compatible with the other solutions? In my opinion, the final solution - require delimiters - is very strongly forward compatible with other options people might prefer.

Forward compatibility with useful precedence

Entirely forward compatible

This is the most straight-forward forward compatibility (😏) of all of them. If we were decide on using the useful precedence, await { e }? would just be the composition of a await, a block, and ?, having exactly the same meaning it has under the required delimiter. All that would change would be that the braces would sometimes become unnecessary.

Forward compatibility with obvious precedence

Mostly forward compatible

This is the one which is least straightforward. Under the most grammatically orthogonal implementation of the obvious precedence solution, await { e }? would have to mean that ? was applied before await, changing its meaning and making the delimiter solution not forward compatible.

However, required delimiters are forward compatible with applying the obvious precedence with the sole exception of braces. In that case, await { e } would not be interpreted the composition of await and a block, but its own syntactic variation on await which has the useful precedence. This is not so different from the fact that if true { e } is not actually the composition of if true and a block, but a brace-using construction of its own. I think, if we decided we wanted await e? to have the obvious precedence, we could easily afford this small irregularity in our grammar around blocks, which I don’t think would actually surprise users any more than the difference between blocks and brace-using control flow already surprises them.

Forward compatibility with postfix syntax

Entirely forward compatible

I’m not personally very enthusiastic about postfix solutions to await, because there hasn’t been a proposal that doesn’t have a really big problem in my opinion. But even if we were to someday have a viable proposal for a postfix await syntax, in my opinion, having a syntax that looks more like the syntax adopted by every other async/await language would still be valuable as an onboarding “easiness” feature. That is, even if its idiomatic to write something like foo()@? instead of await { foo() } ?, having the latter syntax as a synonym helps people coming from languages onboard into Rust.

So yes, I think adding await { foo() } is forward compatible with someday having a postfix syntax as well.

Conclusion

Everyone has their own preference regarding their ideal, favorite syntax, but as a pragmatic step toward stabilization in light of the existing differing opinions, I don’t think there’s any proposal nearly as solid as moving to delimiter-required await. That’s why, I’d propose that we decide to stabilize that syntax, with the possibility of future extension to one of the other alternatives someday later. (It’s also possible, of course, that we never extend it, and delimiters stay mandatory forever!)

The only wiggle in this is that the current macro syntax is being used by the tokio-async-await bridge to define its own macro, which is able to await futures from 0.1. Concurrent with this post, I’ve been laying the groundwork for getting futures 0.3 to run on stable with the necessary prerequisite stabilizations. I think we should be cautious about implementing this change and disrupting that bridge until we’ve gotten closer to stabilizing futures 0.3 (which I’m hoping we can accomplish no later than version 1.33 at the beginning of March 2019).