Await Syntax Write Up
One of the major features the Rust project is working on in 2019 is async/await syntax for ergonomic non-blocking IO. This will be a major unblocker for many users, especially those working on network services in Rust. We'd like to stabilize the "minimum viable product" version of the feature proposed in [async-await-rfc] as soon as possible. You can read more about the feature in general in that RFC.
Other than implementation work, there remains one major unresolved question from the RFC: the final syntax for the await operation. The RFC left the syntax for this operation unresolved, proposing to use a macro — await!(expr) — which was not intended to be the final syntax for the operation. This post is intended as a summary of the situation around the syntax for this operator as of March 2019.
- Note on terminology: In this post, a distinction is made between the “await operation,” the “await syntax,” and the "await keyword.” The operation is the semantic operation of awaiting a future inside an async context; the syntax is the syntax we choose to use for to denote this operation; the keyword is the reserved identifier "await” which may be used in the syntax or not. Sometimes in casual conversation these terms are used interchangeably.
- Note on authorship: Throughout this post, first person statements (we, our, etc) refer to the opinions shared by the authors of the document. Opinions which are the definite consensus of the entire language team are expressed as "the language team believes,” "the language team agrees,” and so on.
The Error Handling Problem
The syntax was originally left unresolved because of a particular “order of operations” problem that emerges with the interaction between the await operation and error handling in Rust. The problem is this: because the intended use case of async/await is to perform IO, the vast majority of futures which will be awaited will return Result; the most common way to handle a Result in Rust is to use the ? operator to unwrap it, “throwing” the error upward. Given this sequence of operations, if we were to mimic other languages without any modification, this would become a very common pattern:
The language team agrees that having to wrap the entire await expression in parens like this would be undesirable, and so we'd like to have a better alternative. Though other arguments in favor of different syntax choices have been raised as well (and will be addressed later), in our opinion this — which we will call the "error handling problem” — remains the primary problem that we are trying to resolve.
It should be noted, as well, that while this error handling problem applies not only to the ? operator but also, less commonly, to methods. For example, a common pattern from failure is to adapt the error using a method called .context, leading to situations like this:
(await future).context("An error occurred.")?;
From Four Solutions to Two
During discussions among the lang team and with the community, a large number of solutions have been proposed. Those which do not involve reworking the entire feature (which is out of scope for this document) general fall into one of four categories:
- Order of Operations Solution: Employ the standard prefix syntax used by other languages and make the order of operations of the await operation unusual for a prefix syntax so that it binds tighter than some postfix operators like ? (and possibly .)
- Syntactic Sugar Solution: Employ the standard prefix syntax, but with some variation (such as a syntactic sugar) that avoids the problem of writing parens around an await expression.
- Postfix Keyword Solution: Employ a "postfix keyword" syntax so that the order of operations with postfix operators like ? and . does not present any problems.
- Postfix Sigil Solution: Employ some sort of postfix sigil, analogous to ?, which also sidesteps issues with the order of operations. The most commonly suggested sigil is @.
Based on a set of shared values, the language team has consensus that we would like to avoid two of the above options, narrowing our choices down to two broad categories.
The Order of Operations Solution is too surprising
The easiest solution to rule out was the solution to modify our order of operations so that await had higher precedence than ? and possibly .. After reviewing the implications of this, we found that we agreed that its implications would be too confusing for users.
It is the standard behavior for Rust and syntactically similar languages for right-affixing operators to bind tighter than left-affixing operators. Though users rarely think about the order of operations of these operators, they come to implicitly expect this behavior. For example, consider this code:
It seems clear here that . should bind to self tighter than await does, just based on the appearance of the code and the behavior of all similar syntactic forms. If we were to bind await tighter than ., however, this would be equivalent to (await self).async_method(). Even if we excluded . from this, there are examples, such as certain kinds of builder APIs, in which this would result in a very surprising parse:
Here, the await binds tighter than the first ?, not the second. Users have come to expect, essentially, that . and ? have the same precedence, and that that precedence is higher than prefixes.
Familiarity argues strongly for using an await keyword
We also have consensus among the lang team that some degree of familiarity with the syntax of this feature in other languages is important. In particular, we note that this feature has come to be identified with the addition of two new language forms: an async operator and an await operator, to the point that the feature itself is referred to simply as "async/await.” We have consensus that this argues strongly against using a sigil syntax, or any selection which does not contain the string "await” as a part of its syntax. For that reason, we have excluded sigil based syntaxes like @from further consideration. If we adopt a postfix syntax, it will include the await keyword in some way.
Prefix vs Postfix