Does `await` truly need fixing?

Rather than yet another thread pushing a new syntax, I would like to re-broach the topic of whether the initial plan of await actually needs fixing at all.

I have spent a lot of time looking at each solution proposed over the last few days, and have come to the conclusion that there are more negatives in them than leaving the syntax as it was originally planned (at least for the time being).

// previous, without ?
let value = await future;
let value = await my_async_function();

// previous, with ?
let value = (await future)?;
let value = (await my_async_function())?;

// new, without ?
let value = future.await;
let value = my_async_function().await;

// new, with ?
let value = future.await?;
let value = my_async_function().await?;

Based on the above, my conclusion is that there is a slight marginal improvement when using ? with the new syntax, which comes at the cost of a worsened syntax when not using ?. Given that it’s debatable whether there is improvement at all (see other threads for those arguments), I claim that the new syntax is actually not a net improvement and that we should therefore keep the original plan for the time being.

In order to enable the new syntax in future, I propose that although it’s dropped from the initial so-called “MVP” implementation of async/await, it is instead introduced in a more general “Postfix Keyword” feature in future when all other supported keywords also receive a postfix implementation. This should satisfy both those who want the new syntax and those who do not, and it’s actually more consistent because it will be like any other keyword and have both a postfix and non-postfix version.

I have written in more detail here for those interested; I wanted to keep it more concise for the forums.

7 Likes

Can you give more details about why await foo is materially better than foo.await? I see the notes that the former “more familiar” and “well regarded”, but that’s a historical argument. What’s the argument that it’s intrinsically better for Rust?

5 Likes

That’s a fair question!

As I’m not qualified to truly speak on it, I’ll defer to the fact that it was originally the working syntax for the feature until the debate with ? came up. As such, there will be documentation on why this was chosen at that point in time by the language team.

Having said that, there are a few reasons I feel it better personally:

First off, I think the familiarity is an extremely good reason to keep that syntax. Rust already has a perceived high learning curve - changing syntax for (what will seem to be) little gain will be off-putting for newcomers. If I see (await foo())? as a newcomer, I know enough to decipher the inside of () and know that something asynchronous is happening in foo(). I don’t know what ? does, but it’s a start. If I look at foo.await?, it’s pretty alien, particularly because it’s not obvious that there are two operators there.

Secondly, controversy is not something we want. The original syntax didn’t have everyone up-in-arms on the forums trying to find alternatives, nor did it have people saying it was confusing/misleading/etc. Regardless of whether you agree with those arguments or not, it’s definitely better to avoid such a divided community. The lower amount of friction in the original syntax makes it favourable (to me).

Finally, there’s more complexity with foo.await. Not necessarily in implementation, but we’re now setting precedence for postfix keywords which is something we probably don’t want to rush. I don’t think forcing that feature through to give us an arguable improvement in ? is necessary, especially given the hard timeline of the async/await feature. Without knowing the implementation, I’d imagine it snowballs what we’re adding quite a lot.

At the eventual end of all this I would expect to have both forms. I’m not arguing against foo.await itself, just against it right now. I believe that foo.await should not be considered any more urgent than foo.match, and that postfix keywords should have their own roadmap and be introduced in a consistent way across the board in one sweep. Naturally this would mean bringing foo.await into the language at that point in time.

11 Likes

Yes, it truly does. It has already been discussed and AFAIK the language team has already made the decision about fixing it. The only remaining issue to be settled is on the details how.

Note that withoutboats who made the current proposal personally prefers the await foo prefix syntax. But they’ve taken into account many other arguments and options on this to arrive at the current state, so it’s definitely not from lack of considering other options.

5 Likes

As far as I’m aware this is not true. The RFC explicitly called the syntax out as an unresolved question because of the potential ordering issue. Where are you getting the impression that prefix-await was ever considered to be an actually proposed syntax? (It may have been used informally in discussion since a lot of people are familiar with it from C#/JS/Python/etc.)

7 Likes

You’re right that I’m talking more about how it has been presented in discussion. Even in the Paper document, that syntax is front and center as the thing to “fix” - which suggests it’s already what we were going ahead with. If it was phrased as “we need to decide on syntax” and it was simply another of the listed options, it would come across differently.

1 Like

One of the listed options in the Paper document is “Mandatory Delimiters”, which is the same as (await foo)? from what I can see. So yes, “fixing” it can mean accepting it as it is in prefix form.

The proposal from withoutboats makes good sense as a middle ground when considering the syntax alone. Given that it’s introducing the dependency of postfix keywords, I’m not sure it should be associated with what has been labelled as an MVP of async/await. Of course, as stated above, adding the proposed syntax at a later date still makes sense.

"Confusing" no, verbose and annoying yes.

I'll note it's hard to measure the controversy with a proposal because those in opposition to the proposal are always noisier than those who support it.

It's done.

8 Likes

The poll has fairly good data on this. See Async / Await syntax straw poll - #50 by tkaitchuck

Personally I have not participated in the poll (and have tried to avoid getting drawn into opinion based discussion about the syntax) because I trust the language team to make a good decision. I know of a few others that are also basically abstaining from the discussion and are happy to go with whatever syntax is chosen. It seems likely that there’s a significant number of people in a similar situation that are not going to appear in any sort of polling.

7 Likes

Of, course. In fact there is a much greater bias, the poll only reflects people actively reading this from. Most users of Rust do not fall into this category. Neither does anyone who will use Rust in the future but has not discovered it yet. It's always important to keep in mind that most of the users that will use the feature haven't found Rust yet.

That can be viewed as a further argument for favoring consistency within the language and with other languages, even if that is not favored by the experts and early adopters. However even in this poll there has been a strong preference in that direction.

2 Likes

Anything in the poll, though, is conditional on people having filled out the poll. If "Postfix await proposed for Rust" shows up on Reddit, the probability of someone who prefers prefix then going and filling out the poll is higher than the probability of someone who thinks "yeah, sounds fine" bothering to fill out the poll.

Maybe that is sometimes true, but looking back of the history of the poll (which you can see in my updates in the thread) The percentage of people who like or dislike the .await syntax hasn't change much. Both before and after the announcement it was not particularly widely liked.

The very first results from the poll had around 25% of people in favor of that syntax and 75% opposed, and it's less that 1% different from that now. So there hasn't been any clear change in response to the announcement.

Let’s just link “the other poll” which is more focused on the path to take right now. Currently, it’s “no opinion” > "fut.await first" > "await fut first" > “neither”. (But also only 35 votes so far, so a very small sample size.)

I haven’t really been following this topic, but this syntax looks totally alien to me. Is ‘.await’ a struct field? Some kind of method call without parentheses? Some kind of keyword with a ‘.’ separator? This doesn’t seem intuitive at all, it looks like some kind special case syntax that’s different from Python and JavaScript or anything presently in Rust.

EDIT: Ok, found the longer thread.

I think I see what they’re going for, but it seems like the original proposal has the benefit of working consistently with other languages while being less efficient to work with. Whereas the new proposal is inconsistent with other languages and Rust’s existing syntax patterns, but is more efficient to work with.

That being the case, I’m not sure. Sorry, carry on I guess…

1 Like

"Historical" arguments matter. I'm really disappointed to see this concern dismissed in this way by you, another member of the lang team. Rust exists within a particular historical context, intended to benefit users who exist within that context, having knowledge, biases, expectations, needs, and so on all determined by that context. Imagining that you are designing a platonic language with "intrinsic" properties detached from its context is a deeply flawed, immature design philosophy.

This is much more important than just the syntax for the await operator; we can't design Rust as if we exist in a vacuum without historical context.

9 Likes

I agree that history and context matters, but they are not everything (not that you suggested that) and I do not read @scottmcm's comment as a dismissal of the concern as if it had no value at all. For example, in the RFC about reserving try { .. }, Scott makes such a historical argument well. Moreover, I read what Scott says as considering the history of Rust. In other words, "intrinsically" is not some sort of idealistic consideration in this context.

Certainly. A materialist philosophy rather than idealism seems prudent in all aspects of life and that includes language design. However, having a historical analysis is not necessarily the same as an argument by and for tradition. For example, taking history, context, and the user base into account, you may find that we want to do something different than what tradition might argue. To make progress, one also has to break with tradition in some cases.

So yes, learn from history, but don't necessarily repeat it.

6 Likes

Coming from a javascript / typescript background I’m a little confused why the ? operator wouldn’t be implied automatically by await prefix notation in the first place.

For example, in typescript an async function would look like this:

async function myFunc(): Promise<void> {
    const res1 = await foo();
    const res2 = await bar();
    return res1 + res2;
}

If either calls to foo or bar fail, the top level myFunc function rejects. This passes the error handling up automatically; much like the ? operator in rust does.

Is it because there is actually two Results in a rust future? One for the Future and one for the actual IO operation? If that were the case it seems like it would still be combined into one.

3 Likes

Because futures are not always fallible, so you can’t automatically apply the ? operator.

Also you may want to perform some actions on error before “bubbling” it up, e.g. see usage of context method in Fuchsia async code snippet.

1 Like