Async / Await syntax straw poll

This poll options are misleading.

let result = (await future).foo() should not be listed out, from C# and JavaScript experience, I’ll always call the method in a separate line in this case. i.e.

let result = await future;
result.foo()

Tha actual code example should be:

let result = await future;
let result = await future?; 
let result = (await future)?;

You see, I support this syntax, the other options won’t beat this.

3 Likes

If the parentheses are redundant, because the await binds tighter than ?, How would you handle a result containing a Future?

How would you handle a result containing a Future?

let res = (await foo?)?;

Is that for a result of a future or a result of a future of a result. If the former why are there two question marks? If the latter, the would the result of a future be:

let res = (await foo?)

But then the parentheses seem redundant, after all there is nothing outside of them. But if you remove them that is the same syntax as for a future of a result. (And so would presumably be a compile error) Leaving no way to actually handle a result of a future.

you could do

let foo: Result<impl Future<Output = Result<T, _>>, _>;

let res : T = await (foo?)?;
// or
let res : T = (await foo?)?;

I cannot emphasize enough how much I believe that await should be an infix operator. Not only is this extremely easy to parse, but it is extremely easy to read as well. Our brains are already accustomed as programmers to read operators:

a.very().long.chain await.another_method()
// => equivalent to the following precedence:
((((((a.very)()).long).chain) await).another_method)()

This gives rise to very natural Result handling as well:

get_result_of_future() await?

Of course, we can do this as well:

get_result_of_future() await.or_else(|_e| Ok(...))?

It can't be infix, because await takes one argument. The syntax you demonstrate is a postfix unary operator.

get_result_of_future() await.or_else(|_e| Ok(...))?

Visual grouping of this is not great. To me it looks like:

(get_result_of_future()) (x.or_else(|_e| Ok(...))?)
2 Likes

Ok. I’ve added “keyword with tight binding” to the poll. I’ve also eliminated the two least approved options. (Existing responses to them are still counted) I just didn’t want to force everyone to Wade through so many options.

You can update your response at any time, if you want to support “keyword with tight binding” or have just changed your mind about any of the options.

I should clarify my terminology: I come at syntax from a parsing perspective, using terminology from Pratt parsing, in which an operator can occur in two positions: prefix, and infix (NUD/LED). You are correct in observing that in my proposal, await consumes no “right” expression, and therefore it is unary postfix.

2 Likes

Quick update on the current standings as there has been a segnifigant shake up.

Current standings. (The poll is still open) With 668 responses:

  • Macro style - 65.8% approve, 32.3% disapprove
  • Keyword (prefix) - 65.4% approve, 33% disapprove
  • Keyword with ? - 29.8% approve, 69.2% disapprove
  • Keyword with try keyword - 18.6% approve, 81.3% disapprove
  • Keyword with tight binding - 31.1% approve, 68.8% disapprove
  • Function style syntax - 41.1% approve, 58.9% disaprove
  • Keyword with optional parentheses - 38.1% approve, 61.9% disapprove
  • Keyword with braces - 36.1% approve, 63.9% disapprove
  • Field access style syntax - 25.8% approve, 73.5% disapprove
  • Field! Access style syntax - 23.9% approve, 75.1% disapprove
  • Method call syntax - 49.6% approve, 50.0% disapprove
  • Postfix macro style - 47.6% approve, 51.7% disapprove
  • @ sigil - 20.1% approve, 79.9% disapprove
  • # sigil - 14.7% approve, 85.3% disapprove

In terms of favorites: Keyword leads with 33.7%, followed by method call style with 25.1% then macro style and Postfix macro style which are tied at 23.3%. Field access style is towards the bottom of the list with just 11.2% favorite.

Interesting notes:

  • Most of the votes have come in since the announcement of the proposed final syntax being field access style.
  • Field access style has not gone up in the rankings.
  • Sigils while popular early on, seem to be very unpopular with the more recent voters. (This may be a difference between those who are reading this form often vs those that wait for the weekly email)
  • The plain keyword syntax still leads in both approval and favorites.
7 Likes

I suspect that the “dislike” values might be more important. Could you please post those as well?

2 Likes

I updated the above post to include this info. (Some number slightly changed and were updated)

1 Like
  • Keyword (prefix) - 65.4% approve, 33% disapprove
  • Keyword with ? - 29.8% approve, 69.2% disapprove

It means 35% of voters approve

(await future)?

but disapprove

await? future

I wonder why. My theory is that await? option is not fully understood.

1 Like

See the paper writeup from the first thread for one possible reason:

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.

Similarly, what expectations might someone have from seeing await? foo()? Would await ?foo() work? (Probably, given our normal tokenizing rules.) Would await (?foo()) work? (Nope, probably not, because it's not a prefix ?, it's a special construct.) Would loop? { ... } work? (I suppose it could? I guess it'd mean loop {...}??)

2 Likes

Would await (?foo()) work?

Nope, because await? is a whole syntax construct, not a combination of ? and await.

Same way as if let is a "keyword", not regular if and boolean let.

Another example is &mut. It is a whole syntax construct; & and mut cannot be interpreted separately. It means "create a mutable reference to data", not "create a reference to mutable data".

let a = &mut b;

is not the same as:

let a = &(mut b);

await? can be called try_await, but await? looks nicer.

Would loop? { ... } work?

It may or may not work, but loop? is a different syntax construct which can be discussed separately.

Similarly if let did not immediately enable while let.

A user who has learned the concept and syntax of ? and of await cannot immediately understand what await? means.

The same reasoning applies to postfix .await. A user who learned the concept of field access and futures cannot immediately understand why field await need to be accessed to suspend the execution of the current future.

That said, I doubt learnability of syntax is an issue. Because learning syntax is hundred times easier than learning underlying semantics (what happens when async is used).

Thank you for the explanation. Not sure it is the whole reason, but at least arguments against prefix await make sense.

1 Like

This is a bad analogy because if let ps = e b (else b)? is soon becoming the combination of if e b (else b)? and let ps = e. In other words, syntactically speaking, let ps = e is becoming an expression. Moreover, the current state of affairs, where if let is a separate construct, is not good. Specifically, the fact that you cannot say if p && let qs = r { .. } makes code read quite poorly in the compiler. The moral of this story is that constructs that don't compose are unfortunate and should be avoided when possible.

This is a good example of when it isn't so easy to make syntax composable because fundamentally the semantic construct is not a composition. await? is not one of those cases. await? is a composition of constructs and it is easy to give it composable syntax.

Is there a TryFuture trait which this operator await? is linked to? Importantly, if let was and is, for perhaps a few more weeks, a separate semantic construct than if. However, await? fut and (await fut)? would be equivalent.

There isn't really a good reason for this aside from "we didn't have time or experience", and the former rather quickly begat the latter.

3 Likes

However, await? fut and (await fut)? would be equivalent.

Is there a TryFuture trait which this operator await? is linked to?

There is none. But the same way there is nothing special about ? operator, it is just syntax sugar to

match expr {
    Ok(r) => r,
    Err(e) => return Err(e.into()),
}

So technically ? is redundant.

So await expr can be a native operation, while await? expr can be desugared to (await expr)?.

The moral of this story is that constructs that compose are unfortunate and should be avoided when possible .

I agree with these arguments.

It's just I think general syntax consistency/symmetry (keywords are almost always prefixed, e. g. do not have x if x >= 0 else -x or expr.match { ... }) overweights the redundancy of await? operator.

BTW, I just realized that poll done by @tkaitchuck has no intrinsic function option. Which is

fut.await()?;

where await is not a keyword, but an intrinsic function in Awaitable trait. This might be a compromise for everybody: it allows chaining, it is composable with ? and it does not break core language syntax symmetry.

1 Like

Incorrect. ? uses the Try trait, and behavior is customized by using that trait:

match Try::into_result($expr) {
    Ok(ok) => ok,
    Err(err) => return Err(From::from(err)),
}

Your clarification does not contradict my argument that ? is just syntax sugar and so could be await?.

Beside @CAD97's point, it seems clear to me that e?, lhs && rhs, lhs || rhs, if c { bi } else { be }, and so on, justify their existence by providing enough semantic compression to be worthwhile as syntactic sugars over match. This is unlike await? expr, which offers no compression over expr.await? (both are exactly of the same length), which does not add additional syntactic forms over expr.await and ? themselves.

5 Likes