Advertisement
Guest User

Untitled

a guest
Apr 30th, 2019
691
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.10 KB | None | 0 0
  1. Await Syntax Write Up
  2. 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 [RFC 2394][async-await-rfc] as soon as possible. You can read more about the feature in general in that RFC.
  3.  
  4. 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.
  5.  
  6.  
  7. > 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.
  8. >
  9. > 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.
  10. The Error Handling Problem
  11.  
  12. 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:
  13.  
  14.  
  15. (await future)?;
  16.  
  17. 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.
  18.  
  19. 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:
  20.  
  21.  
  22. (await future).context("An error occurred.")?;
  23.  
  24.  
  25. From Four Solutions to Two
  26.  
  27. 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:
  28.  
  29.  
  30. - 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 `.`)
  31. - 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.
  32. - Postfix Keyword Solution: Employ a "postfix keyword" syntax so that the order of operations with postfix operators like `?` and `.` does not present any problems.
  33. - 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 `@`.
  34.  
  35. 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.
  36.  
  37.  
  38. The Order of Operations Solution is too surprising
  39.  
  40. 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.
  41.  
  42. 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:
  43.  
  44.  
  45. await self.async_method()
  46.  
  47. 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:
  48.  
  49.  
  50. await self.builder().fallible_config()?.finish_future()?
  51.  
  52. 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.
  53.  
  54.  
  55. Familiarity argues strongly for using an await keyword
  56.  
  57. 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.
  58.  
  59. Prefix vs Postfix
  60.  
  61. The two remaining categories of solutions have been described as "prefix” vs "postfix” syntaxes, because in one category await prefixes its expression, whereas in the other it postfixes it. Its worthwhile now to enumerate several of the options in each category. However, this document will not attempt to weigh in great detail the arguments for each.
  62.  
  63. Prefix
  64.  
  65. If a prefix syntax is selected, the options mainly have to do with resolving the problem of supporting the `?` operator and possibly at least some uses of `.` operator, as discussed previously. There are several directions that could be taken to resolve these problems using prefix syntax.
  66.  
  67. | Name | Description | Supports ? | Supports . |
  68. | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | ---------- |
  69. | Mandatory Delimiters | Make await followed by delimiters (such as parens or braces) which enclose the expression it applies to | Yes | Yes |
  70. | await? | Provide a syntactic sugar combining the await and try operations as `await?`. | Yes | No |
  71. | Optional Delimiters | Support await without delimiters, but make delimiters following await which bind to await tighter than postfix operators, in effect behaving like mandatory delimiters | Yes | Yes |
  72. | Add Future trait adapters | For common adapters which are implemented on Result, also implement them on futures of Results (as an extension trait). | No | Partially |
  73.  
  74.  
  75. Though some of these options are mutually exclusive, not all of them are, and it is possible to choose multiple of them.
  76.  
  77. Postfix
  78.  
  79. For postfix syntax, the question is not how to solve the error handling problem (because postfix syntax inherently resolves that problem), but which specific postfix syntax to use. There is not precedent for a postfix keyword operator in Rust, leaving many possible options:
  80.  
  81.  
  82. - Field access syntax: Make await use the same syntax as field access, but with the await keyword — `future.await`
  83. - Method call syntax: Make await use the same syntax as method calls, but with the await keyword — `future.await()`
  84. - Postfix macro syntax: Make await look like a macro called as method syntax (a feature which doesn't otherwise exist currently) — `future.await!()`
  85. - Space syntax: Make await succeed the expression with whitespace and no other tokens between — `future await`
  86. - Unusual other syntaxes: Use a more unusual syntax which no other construction in Rust uses, such `future!await`.
  87.  
  88. Members of the lang team who prefer a postfix syntax are divided on which postfix syntax is most preferable (though those who prefer a postfix syntax typically prefer any postfix syntax to any prefix syntax), but the postfix syntax with the greatest level of support in the lang team is the field access syntax.
  89.  
  90. Ergonomic & Readability Considerations Beyond Error Handling
  91.  
  92. A lot of attention has been given to the question of ergonomics and readability beyond just the error handling problem. However, we do not have consensus as to the conclusion to be drawn from this, and in many areas it seems to be a matter of taste to some extent.
  93.  
  94. One particular point of dispute, both within the team and the community, has been the matter of readability and code clarity. The argument has been made that because await modifies the user's interpretation of the entire expression it is applied to (i.e. recognizing that that expression will be evaluated as a future), it is valuable for it to appear at the beginning of the expression. Because prefix await discourages nesting await inside of other expressions, the await will appear very early in the statement, informing the user's understanding. Similarly, but distinctly, it has been argued that this "up and front await” is valuable not because the awaiting itself is important, but because expressions being awaited are themselves likely to be important (because they perform an expensive operation such as IO), and the await "highlights” them for readers trying to understand the piece of code.
  95.  
  96. On the other hand, an argument in favor of postfix syntax highlights that, since many Rust expression forms are right-affixed (in particular, method calls and the `?` operator), postfix await makes it more likely for the await expression to appear “in sequence,” appearing syntactically subsequent to the operation that proceeds it and precedent to the operation that succeeds it. Users making this argument find that this helps them to read and understand these expressions because it reduces the degree to which they need to "re-order” the expression into a sequence of steps in their mind.
  97.  
  98. A similar syntactic evolution supported the switch from the prefix `try!` macro to the postfix `?` operator for error-handling. Reading or writing an expression involving the `try!` macro involved jumping back to the beginning of the expression for the `try!`, then back to the end of the expression to continue chaining.
  99.  
  100. Along the same lines, it is generally agreed that postfix syntax makes it easier to embed await expressions inside of other expressions in a point free style, such as `foo.await.bar` or `foo(bar.await)`. It's a point of disagreement how frequently users will want to do this beyond the error handling case, and also whether or not the language should encourage this kind of code. Some find it convenient and elegant, whereas others find it confusing and opaque.
  101.  
  102. One point of general agreement is that it is slightly easier to write the await operation in postfix syntax. If you forget to write the await operator under prefix syntax, you need to move the cursor back to the beginning of the line. It's been argued that postfix is also better for IDE integration — because it will be an option when the user types the `.` operator — but there are other alternatives: for example, IDEs can include await in their drop-downs even if it means a rewriting of the expression to insert a prefix `await`.
  103.  
  104. Different members of the language team evaluate these considerations very differently, reaching different conclusions not only about which syntax they favor, but also how important these other considerations should be in influencing our decision. Some members of the team feel that these arguments strongly favor a postfix syntax, whereas others find the advantage uncertain; some feel the advantage here is a key argument in favor of postfix syntax, whereas others feel they are outweighed by other considerations.
  105.  
  106. Conflicting Visions of Consistency
  107.  
  108. One of the largest unresolved disagreements among the lang team concerns the matter of consistency, which of these selections would be more or less consistent. Many members of the language team place a high value on consistency as a determining factor in this decision. However, the very nature of what it means to be consistent, and what sorts of consistency are important or unimportant, is in dispute in this case, making it an especially thorny disagreement to resolve.
  109.  
  110. Syntactic Precedent and the Weirdness Budget
  111.  
  112. On the one hand, supporters of the prefix syntax are concerned that there is no precedent in the syntax for a postfix keyword operator. All unary keyword operators in Rust are prefix, and with the exception of the infix `as` operator, all constructions (at the item, type, and expression level) involving keywords involve a keyword as the initial token of the sequence. Along the same lines, constructions involving keywords as a rule do not require punctuation characters other than braces, whereas most favored postfix syntaxes require some extra punctuation between the expression and `await` (e.g. the period in `foo.await`). These two factors make postfix await syntax a significant divergence from Rust's syntactic history.
  113.  
  114. Moreover, prefix await is the syntax chosen in other similar languages (such as C#, JavaScript, and Python) with an await keyword. Beyond that, this camp argues that precedent from languages with C-style syntax generally strongly favors unary keyword operators as prefix, and has little precedent for keyword operators involving punctuation characters other than delimiters. Because of this precedent not only from Rust but also other syntactically similar languages, this group finds it difficult to accept a syntax which which looks like a different, non-keyword construction, such as a field access, method call, or postfix macro.
  115.  
  116. For some who hold this form consistency as important, it seems to be of paramount importance, affecting even Rust's credibility with end users as a seriously considered and well designed language. Deviating from this precedent of Rust and its syntactic family, they argue, would require enormous justification. One argument taken along these lines is a notion of a “weirdness budget”: that a language can only deviate from users' pre-existing expectations so much before it becomes too “weird” and loses acceptance from many potential adopters.
  117.  
  118. Coinciding with this reasoning, it is argued that Rust has already "spent this budget" to a great extent, partly in essential forms of “weirdness” (such as new type system constructs needed for its design constraints), but also in non-essential ways (like making novel keyword choices when longstanding precedents existed, having an unusual module system, and introducing many features not common to mainstream languages all at once). Even if these choices were all correct, the argument goes, our weirdness budget has been stretched to the point that we must weigh the weirdness of deviation from syntactic precedent in this case even more highly.
  119.  
  120. Supporters of this argument conclude, then, that introducing a piece of syntactic sugar to solve the very common interaction with `?`, and accepting what they see as a slight degree of inconvenience in other situations (in some cases, they are also likely to argue, making the code more clear), is better than deviating from syntactic precedent in this way.
  121.  
  122. Feature Orthogonality
  123.  
  124. Most Rust language features strive for orthogonality: once you learn feature A, and feature B, you can typically use A and B together without learning additional special-cases. For instance, once you learn how modules, namespacing, and scopes work, you can apply the same rules to reference a constant, function, type, or any other name from another module, using the same rules everywhere. Because of this, learning and using orthogonal language features takes effort proportional to the number of features.
  125.  
  126. Non-orthogonal features, however, require much more effort for developers to learn and understand. If modules, namespacing, and scopes were non-orthogonal, users would be required to learn separate rules to reference a constant, function, or type. Unlike orthogonal features, non-orthogonal language features takes multiplicatively more effort depending on the interactions between features.
  127.  
  128. A postfix `await` construct and the postfix `?` operator interact orthogonally. If you know postfix `.await` (as one example syntax), and you know postfix `?`, then you can immediately understand that `foo()?.await.bar()` means exactly what it looks like: call `foo()`, then apply `?` to the result, then await, then call `bar()`. It’s also easier for developers to understand and distinguish more complex interactions between features, such as `foo()?.await.bar()` vs. `foo().await?.bar()`.
  129.  
  130. The `await?` construct, proposed to resolve problematic interactions between a prefix `await` construct and the postfix `?` operator, does not have this property of orthogonality. A user who has learned the concept and syntax of `?` and of `await` cannot immediately understand what `await?` means. `await?` introduces a new, special-case syntactic usage of `?` that doesn’t directly follow from existing developer knowledge of `await` and `?`. Furthermore, it isn’t immediately obvious from the syntax `await? foo()` whether the `?` occurs before or after the `await`.
  131.  
  132. For proponents of the postfix syntax, the ability to chain `.await`, method calls, and `?` is not just a goal unto itself, but also a demonstration of orthogonality. Non-orthogonality and special-case constructs make Rust and `async` /`await` harder to learn and use, and result in strange syntactic constructions like `await? (foo()?)` that stretch Rust’s “weirdness budget”.
  133.  
  134. Next Steps
  135.  
  136. Overall, we are shooting to stabilize async-await by 1.37, which branches on July 4th, 2019. This stabilization will include only free and inherent async fns, naturally, and doesn’t represent the end of the road — more the beginning. There remains feature work to be done (e.g., async fn in traits) and also impl work (continued optimization, bug-fixing, and the like). Still, stabilizing async will be a major milestone!
  137.  
  138. For that to happen, though, there is general agreement that we should first resolve the syntax (as opposed to stabilizing the `await!` macro). This has been a difficult issue to resolve. The current plan is as follows:
  139.  
  140. [x] Post this summary and request feedback (if you’re reading this, that step is presumably done)
  141. [ ] In the meantime, boats is writing a series of blog posts exploring the interaction of `for` loops with await syntax (the first blog post is available here), which is a pending topic.
  142. [ ] At the upcoming lang-team meeting on May 2, we plan to discuss async-await syntax, specifically the interactions with `for` , but possibly including other comments.
  143.  
  144. [async-await-rfc]: https://
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement