A final proposal for await syntax

I don't think that this is good sugar because it isn't extensible. For example, what if we add a new operator ?break (actual operator isn't too important) that breaks instead of returning. Should we add await?break as well? What about some other operator we haven't yet thought of?

If we get resume arguments then yield may become postfix. But even if that doesn't happen, there is a semantic difference, with yield it is more important what you are yielding, so being consistent with other things that return a value is good. With await it is more important what you get back, and there isn't anything quite like that in Rust today.

But await always uses what is passed in to determine the output but yield has no such guarantees.

2 Likes

Then you're arguing for postfix .break, too? Because right now the plan is to support only postfix await. I we do plan to support other postfix keywords, I'll argue that await stabilization should be postponed until after a postfix keyword RFC. I'd like that anyway, actually.

I don't see what you return or get back as being that important. Sure, you're using the value, but the suspension and resumption of a function are actually the most important thing. You don't actually need generator resume arguments, for example, as you can stick the value either in TLS or in a box, then retrieve it from there.

And while I admit that most people will use them like you mention, it's an artificial distinction that's actually detrimental to someone trying to understand the language semantics.

That may be true in some cases -- consider combinators, for example, which don't actually know what they're waiting for. The only guarantee that async methods and generators actually have is what the type system has to offer.

I think it's a subtle distinction, and it's not immediately obvious that one specific syntax is better than another just because of it. There's certainly a cost in using different syntaxes for it.

1 Like

This is not guaranteed, in the original implementation yield was a keyword that took in the value to yield while gen args was a "keywords" that gave you the current resume argument.

There are a couple of major reasons that yield returning the resume argument doesn't work:

  1. How do you get access to the resume argument before the first yield?

  2. What is the type of the value returned by yield? Somehow it should involve a lifetime that ends before the next yield so that you cannot keep resume arguments across yields (important for scenarios like Future where the resume argument would be a &mut Context if it were implemented on generators). It seems much less "magical" if you have a separate "keyword local" that allows you to borrow the resume argument but doesn't let you move it (whereas yield returning the resume argument would be a move).

3 Likes

I haven’t seen a link to the Lang Team Meeting 2019.05.02 in this thread so far.

That link jumps to 14:20 when discussion of async/await starts.

7 Likes

No because of stability guarantees, but if we redo it, still no because break returns ! Btw, I said ?break a variant of ? that breaks intead of returning, not break.

That's a pretty big guarantee.

That's why await is a keyword.

I am aware of that and wasn't arguing that.

How is this detrimental? I'm curious, I don't see what's so bad about it.

1 Like

It occurs to me that, given the allowance for whitespace around dot syntax, that I could achieve some distinction from field access, even with the current .await just by adding a leading space, at least when formatted inline. This is similar to @CAD97's previously suggested formatting but more opinionated. As I doubt rustfmt will be changed to support configuration for this variant, this will be just one more reason not to use rustfmt on such code, and one more reason to avoid shared maintenance. The same approach looks even better with the alternative sigils, see below.

spacedot .await

postbang ¡await

cuttlefish @wait

"cuttlefish" is suggested syntax nickname, for format variant of @HadrienG's @wait suggestion which he volunteered to implement in rustc.

Here is a screenshot of ¡await highlighting in emacs, dark scheme, via customized rust-mode.el:

Numbersign # is a nicer sigil, in principal, than atsign @, but #await gives emacs rust-mode trouble for auto-indentation. So that would be another thing to fix if this alterantive was accepted.

“Space symbol await” has the same grouping problems that “space await” does.

And if “sigil on the majority of keyboards” doesn’t happen, “sigil not on the majority of keyboards” definitely won’t. I can safely say that ¡ is never going to happen, nor is any other alternative that involves a symbol requiring compose or dead keys or any kind of modified keyboard layout to type.

21 Likes

Well, a leading space must be legal syntax (e.g. for multi-line chaining), and without rustfmt it will be used.

Seems like I must be missing something. Can you point out at least one example of the “grouping problems” in the below source file? I don’t know why that wouldn’t have jumped out at me by now.

This source uses .await 39 times in 529 lines of rust code, 12 async fns/blocks.

Yes, ¡await is more just a testimony to what a committee design process can’t produce. Committees instead produce designs like the 2018 Chevy Cruze exterior and .await.

Is that a Chevy Cruze ad?

By "grouping problem" I suppose you mean this bullet point from the original doc

  • It does not visually group well, making it seem like await and anything attached to it are a separate expression from the preceding subexpression.

This is relevant only when you write the entire chained expression in one line

let x = foo.bar .await?.as_ref();

let x = foo
    .bar .await?
    .as_ref();

A similar formatting situation actually happened in RFC 2522 (Generalized Type Ascription) which supported chaining type ascription x : T together with method calling.

let x = foo.bar : Option<_>.as_ref();
let x = foo
    .bar : Option<_>
    .as_ref();

There seems no consensus on this yet because the discussion won't happen before the RFC is approved, but the RFC suggests single-line expressions should be formatted differently from the multi-line case, e.g. there are likely mandatory parenthesis after rustfmt:

let x = (foo.bar : Option<_>).as_ref();
let x = foo
    .bar : Option<_>
    .as_ref();

The format differentiation is applicable here as well.

let x = foo.bar.await?.as_ref();  // no space in single line
let x = foo
    .bar .await?                  // space in multi line
    .as_ref();

(same argument for space-await BTW)

let x = (foo.bar await?).as_ref();  // parenthesis in single line
let x = foo
    .bar await?                     // no parenthesis in multi line
    .as_ref();
1 Like

space-await and space-dot-await both have the problem of not appearing visually grouped with the expression they apply to, or the subsequent items applied after them.

Yes, exactly.

Which should remain a possible formatting, for short expressions.

If the space isn't mandatory, then this isn't a syntax question at all, it's a formatting question, and we're intentionally deferring formatting until after syntax gets settled.

For my part, I wouldn't have any objection to seeing .await on the ends of individual lines, much like ?, but I wouldn't want a space before it.

4 Likes

Correct, I wasn't suggesting any change from final proposal. Just pointing out that whitespace is already valid for . chains and would presumably remain valid for any alternative sigil chains.

Why I think its relevant is:

  1. It makes me slightly more comfortable if I'm stuck with .await, because I can differentiate from field access via the allowed spacedot format.
  2. It makes alt. sigils like @wait or #await or (your favorite) ¡await look even better from a typography/formatting perspective.

The Fuchsia code offers a better evaluation base than "foo.bar" code, IMO, and its at least an interesting data point that there was no "grouping problem" in 39 usage sites. Coincidental?

Could ! be used instead of “.”? So, it would look like this?:

let x = foo.bar()!await.blam()!await?.purge()?;

-OR-

let x = foo.bar()!await
           .blam()!await?
           .purge()?;

-OR EVEN-

let x = foo.bar()
           !await
           .blam()
           !await?
           .purge()?;

Basically, “!”, when used in a binary context would become the “post-fix keyword introduction sigil”. Since, “!” means “Strong Statement”, it somewhat aligns with this interpretation.

There seems to be A LOT of objection to using “.” as the separator. Personally, I don’t find it all that bad. I’m fine with interpreting “.” to mean, any of the following:

  • somestructinstance.fieldname : field access
  • somestructinstance.method() : call the method of struct with struct instance as the Self/First parameter
  • somefunctioncall().keywoard : apply keyword to the function call before calling the function
  • somestructinstance.somemethod().keyword : apply keyword to the method called on somestructinstance with the somestructinstance as the first/self parameter to somemethod before calling the method

This all seems legit and I feel could be taught explained and isn’t “having field access mean something different” because “.keyword” is not “field access” it’s “apply keyword to preceding method call”.

That being said, if most in the community are strongly against “.keyword” why not “!keyword”?

1 Like

Using ! (replacing the .) as the leading sigil, as in !await causes some parsing problems for me, but I have a dyslexic tendency. That is why I think all of @ # ¡ are better alternatives, visually for sure, and also when you consider keyboard entry and other current use in the language.

Leading ! sigil was mentioned in the below, “Unusual other syntaxes.” I would love to know the history of that proposal.

Large parts of Rust's tooling, namely Cargo and Rustup, exist primarily to allow people to be productive in the language without having to make global configuration changes to their operating system. Losing this easy onboarding process, and forcing people to engage in extensive copy and paste, or typing unicode scalar values, or change their keyboard layout, seems like a very high cost just for slightly better syntax.

I could type ¡ by just using RightAlt + 1, but I'm using Colemak. We can't expect everyone to do what I do. I'm an early adopter, and most of Rust's users aren't going to be early adopters.

I'm on team just use (await expr)? and deal with it, by the way, but I'd take almost anything else, including expr.await?, over taking Rust's core syntax outside of the ASCII plane. There is text tooling that still uses Latin-1 in production in places, and forcing people to adopt 8-bit-safe transfer protocols is not one of Rust's design goals.

6 Likes

One thing I’m noticing about .await syntax samples is it’s causing me to no longer read code from left to right. I’m now scanning to see where execution is being deferred instead of going through the code step by step like I normally would

4 Likes

Tilde ~ was in prior use for box, and circumflex ^ both have similar challenges as ¡ for keyboard entry, globally. The postbang ¡ is unique among alternatives as not having any prior use or current meaning in rust source code, which seems like a practical consideration.

But okay, you’re welcome to your own value system—there are all-ASCII @wait or @await or #await options as well. All are better than .await from both initial comprehension and learning, as well as long term productivity perspectives, IMO.

Many things could be done. This topic is innundated with things that could be done.

There’s a severe lack of new information or new proposals, however.

8 Likes

Yep, I get that and didn't intend my comment to be a suggestion of something never proposed. I was trying to say that it seemed like "!" has been discarded without a real solid justification and that many (possibly most?) would prefer it to "." and it might possibly be easier to teach and differentiate. It doesn't seem like a very out-of-place use of the "!" operator and seems quite consistent with it being used "post-fix" unary for macros, pre-fix unary for "NOT", and in-fix binary between a method call and a keyword (or, alternatively, consider it pre-fix of a keyword if that works better grammatically).

And tilde is no longer in use, and l ^ r can be written l.xor(r). What was before 1.0 (and especially that which was removed) shouldn't be a basis for stable syntax decisions.

If you drop the assumption that . is for field access only (it's not! It's also for method call syntax, and implies both auto ref and auto deref (which runs user code) currently; it does a lot), it has no problems over any of the other symbols you've suggested. (In fact, it generalizes nicely as a pipeline with scoped name resolution.)

What concrete benefits does using a symbol other than . have, other than it doesn't "look like a field access" and there hasn't been the vocal pushback?

3 Likes