The problem of the current await!()
macro
I think it would help people understanding the situation better if the write-up elaborate more about this topic. I actually didn't see the problem of current async!()
macro approach until I see @Centril's comment.
Which says that await cannot be implemented as a simple macro. Thus, even though we choose the current await!()
syntax, it is not an actual macro but a compiler builtin that could not be written in the standard macro definition. Actually, Rust already have some of them: compile_error and assert. Now the question is: "do we want to put await
on the same level with compile_error
and assert
?" My answer is no, because I strongly feel that async/await is a much more important feature than those and deserves a dedicated syntax.
#1 #2
Prefix vs Postfix
I once wrote a code which reads a row from a database and extract one of its field. If that was written in an asynchronous postfix await syntax, the code would be something like this:
let player_money = DB
.player_table()
.get(player_id).await?
.money;
In prefix syntax:
let player = await? DB
.player_table()
.get(player_id);
let player_money = player.money;
Rust uses a method chaining a lot. Postfix macro is useful for a method chaining after await as discussed in the write-up, but I claim that it is also useful when a method chaining happens before await. I prefer postfix syntax over prefix syntax because it fits better to my mental model. When I wrote these examples, my thinking process flowed like this: "I have a database... now I have an access to the player table... and I want to get a row with this player ID... oh, that was an asynchronous method so I have to await it..." When I realize that a method that I was typing is asynchronous, I can add the postfix right after the method call. On the other hand, in prefix example, I should go back and forth to fill that missed await?
.
That said, I think prefix variants with and without await?
are both acceptable as long as it has the conventional operator priority.
Formatting postfix await
I believe whichever postfix variants we choose, we should keep that on the same line to the async method when formatting. This gives await keyword an exceptional feeling even if it resembles the existing language construct, which helps preventing misconception of the await keyword as a field, a method, or a macro.
Inconsistent candidates (field access, method call, and postfix macro)
I want to emphasize that how a syntax feels when I see the definition of it("field access syntax"), look at a short code(let val = fut.await?
), and see examples from an existing codebase were all different to me. At first, I was very concerned about the inconsistency. However, after trying a few codes with them, I start feeling okay with the choice.
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.
That doesn't mean the inconsistency problem can be overlooked. The biggest problem of field access, method call, and postfix macro syntax is that they look like normal field access, method call, or macro. As noted in the write-up, using these candidates possibly harm the credibility and reputation of Rust. My first impression about them was the same with the above quote. This kind of negative first impression may have adverse effect on promoting Rust to other people.
Field access vs Method call
Right after reading the write-up, field access syntax felt like the worst of the suggested postfix syntax. Await is a very complex operation while field access is one of the cheapest operations in Rust. Their definition didn't feel align.
However, when I compare the candidates with actual Rust codes, I realized field access syntax looks better than method call syntax. For me, distinguishing field access style await from an actual field access was quicker than distinguishing method call style await from an acutal method call. One of the reason for this is the presence of ?
after .await
. Field access usually doesn't have following ?
while method call and await operation often have following ?
. Reading .await?
gives me weird feeling and reminds me that it's an await operation, not a field access.
/* multi-line examples */
// field access syntax
let player_money = DB
.player_table()
.get(player_id).await?
.money;
// method call
let player_money = DB
.player_table()
.get(player_id).await()?
.money;
/* one-line examples */
// field access syntax
let response = reqwest::get("https://httpbin.org/ip").await?;
// method call
let response = reqwest::get("https://httpbin.org/ip").await()?;
#3 #4
Postfix macro
- If there exists general postfix macro: similar problem with
await!()
macro
- If there is no postfix macro except await: gives false impression to people that there exists a postfix macro feature
I think in either case, postfix macro is not a good choice.
Unusual other syntaxes
If Rust chooses unusual await syntax such as future!await
or future@await
, it can naturally bypass inconsistency problem. However, symbols such as !
or @
are too arbitrary for await operation. People would be curious whether there is any other operation in !op
or @op
form or not. Thus, I think the symbol used in this syntax should be as little obstrusive as possible.
I want to suggest ..await
. There was a similar suggestion, but it didn't get much attention. ..await
has similar advantage to field access syntax but doesn't have the inconsistency problem. Also, ..
here gives an impression that await operation takes some time.
let player_money = DB
.player_table()
.get(player_id)..await?
.money;
let response = reqwest::get("https://httpbin.org/ip")..await?;