Update on the await syntax after May 23 meeting

56 Likes

I am glad the team has been able to bring this issue to a reasonable conclusion. :+1:

20 Likes

Totally agree with the way you handled everything. I understand that a lot of people are probably unhappy with the final decision, but still as you said trade-offs + we can’t delay this forever and every time looking at different solutions and not making a decision. I honestly didn’t have any strong opinion on the syntax, but I did like the discussed once and I knew that the end one will be in the best of interest for the Rust language.

7 Likes

It was possible to delay the decision for a long time by adding a macro, and replacing macro with stable syntax any time later. ? operator was discussed and test driven for a long time while rust had try! macro.

6 Likes

As was pointed out in a previous thread, the cost of migration from try! to ? was nontrivial (small ecosystem split from people who were unhappy that their side of the syntax bikeshed “lost”, or simply did not have time to bother switching the code from one syntax to another, invalidation of old “best practices”…) and should not be overlooked when reviewing this past example.

12 Likes

Incorrect. A macro that influences control flow isn't possible without an underlying syntax to expand to:

macros can only influence control flow by expanding to code that the user could have written themselves

3 Likes

Macro could use any unstable syntax (even .await), and that underlying syntax could be changed any time later.

2 Likes

#[allow_internal_unstable] avoids leaking instability if necessary.
It's used pretty widely by stable standard library macros.

(EDIT: That's not a support for keeping await!(...) from me, just a fact.)

14 Likes

The main reason we rejected using a macro is as @HadrienG said. However, I’d also note that all of the proposals that involve macros (including not only this one but also the proposals to have a prefix operator and introduce postfix macros) seem to ignore that macros cannot have keywords as names, and await is intended to be a keyword. Obviously we could call this macro something other than await, but I notice that never even comes up when people propose a macro as such a simple and obvious solution.

8 Likes

As @petrochenkov mentioned, on the contrary this is often how standard library macros work. To give a concrete example, vec! is definitely a stable macro but expands to definitely unstable syntax here.

11 Likes

This is at least partially complicated by the use of await! as the nightly experimental syntax, so proposals naturally intuit that they can say "just do that", since it "already works".

Thus, I henceforth propose that all unstable std/core macros should be called bikeshed_placeholder_for_rfc_nnnnnn! to avoid this issue. /s

10 Likes

As far as I can see, there are potential solutions to that. I'd be surprised if it weren't possible to hack the lexer and/or parser to treat await as an identifier only when followed by !. Alternately and mostly equivalently, we could make .await!() a special syntax equivalent to prefix await, and additionally add postfix macros with similar syntax to 'justify' .await!() to Rust users. I am not actually advocating for those approaches; I like .await better, and it's too late for further bikeshedding anyway. I'm just saying that I don't think the keyword issue would have been the hard blocker you're portraying it as.

6 Likes

This is absolutely possible and could be done by essentially extending https://github.com/rust-lang/rust/pull/60586/files#diff-da9d34ca1d0f6beee2838cf02e07345cR3024 to expect ! ( ) after .await. All you need is essentially:

    fn parse_dot_suffix(&mut self, self_arg: P<Expr>, lo: Span) -> PResult<'a, P<Expr>> {
        if self.span.rust_2018() && self.eat_keyword(keywords::Await) {
            self.expect(&token::Not)?;
            self.expect(&token::OpenDelim(token::Paren))?;
            self.expect(&token::CloseDelim(token::Paren))?;
            let span = lo.to(self.prev_span);
            let await_expr = self.mk_expr(
                span,
                ExprKind::Await(ast::AwaitOrigin::FieldLike, self_arg),
                ThinVec::new(),
            );
            return Ok(await_expr);
        }

However, this also loses all integration with name resolution and so .await!() is a macro in name only. At that point, given that .await is a field in name only and .await() is a method in name only, I think it simply comes down to .await? being nicer to read and write than .await!()?.

7 Likes

Or to put it more succinctly: An argument for can is not an argument for should.

12 Likes

Is there any way for user to implement await keyword as prefix in program, for some people postfix looks strange, ugly and counterintuitive.

It’s very easy to write a macro:

macro_rules! wait {
    ($e:expr) => {
        $e.await
    }
}

Now you can use wait!(foo) instead of foo.await

There’s even a crate which does this for you.

There’s also a crate which adds in implicit await (if you prefer that).

6 Likes

Sure. But it's very hard to write a macro which will be used by code not written by you. E. g. in third-party libraries.

You didn't get the point. A macro could be used to postpone syntax decision.

If .await syntax is final, then the macro is pointless.

2 Likes

I don't understand your point. The macros are used exclusively inside of your code (specifically inside your async functions).

It has absolutely no effect on anybody who is using your library, because it's an implementation detail.

In other words, there's literally no difference between using wait!(foo) and foo.await (other than the syntax)

No, that was not their point. They asked for a user-based way to use prefix await, and I gave them one. They mentioned nothing about the official decision.

The decision has been made, it was carefully considered, and it will not be postponed. It's over.

No, it isn't pointless. People who prefer the wait!(foo) syntax can use it instead of foo.await.

That's the beautiful thing about macros: you can use them to change Rust to fit your personal preferences.

10 Likes

I read the source code of other libraries a lot. Sometimes I copy-paste the code. Sometimes I work with my colleagues how may find the idea of introducing a macro just to wrap the keyword not very smart.

It was just three weeks between prototype committed to rust repo and final decision. Other changes in Rust, especially major like this, bake for months in unstable. It was not carefully considered.

Sure.

But people who prefer single code style across the code base will use .await syntax even if they prefer some other syntax.

This is not what macros are for. Macros are for extending rust, not for having a different syntax of already existing language constructs.

4 Likes

They could just as easily use wait!(...) everywhere instead of .await

This is blatantly false, there was extensive discussion since the initial RFC for async/await. If you were late and only heard of this via the Rust Internals post a few weeks ago that's too bad (Rust cannot wait for literally everyone's opinion because new people come everyday), but you cannot say that there wasn't enough discussion.

edit: see @Pauan's comment below for a brief history of async/await

Actually, you can't really extend Rust with macros, you can remove boiler-plate (think lazy_static) and make the syntax nicer (subjective, thing DSLs), but you can't extend Rust's capabilities.

You could remove macros from Rust entirely and not lose anything other than some efficiency where the macro wraps some nightly feature, but every non-std macro can be removed without restricting the language. Now, it would be significantly more painful to write Rust if you remove macros (because of lots of boilerplate for things like lazy_static or similar), but you could do it.

9 Likes