Await Syntax Discussion Summary

Here’s a completely new idea: Add a syntactic sugar

foo!bar

That expands to

bar!(foo)

Then we could write prefix await as well as postfix await:

foo!await
await!(foo)

Also, it would be orthogonal and general. And the syntax could be useful in many other situations:

foo!dbg.bar();
"Some string"!foo

It would require an RFC for postfix macros, but in the meantime we could stabilize the await!() macro.

I think you forgot about future await, which would also work out fine.

1 Like

I’m pretty sure that majority of people would like the syntax with await applied somewhere.
Life is difficult…

1 Like

See this post. :slight_smile: And I think it may have been proposed in the earlier discussion as well.

What I proposed can be applied to any macro that expects one expression. It’s nothing specific to await.

Regarding the comments that a sigil-only syntax was dismissed too easily on the grounds of await's familiarity:

I think it’s important to point out that the "await is familiar" argument is strongest for people not posting in this thread. The more of a Rust “power user” someone is, the more they’re going to tolerate deviations from what’s familiar in other languages. The less familiar a (potential) user is with Rust, the more of a problem each deviation is (especially if they aren’t using Rust at all yet, but will consider it in the future). Personally, I believe the Lang Team is correct to rule out sigil-only syntaxes for this reason, even though sigil-only syntaxes don’t bother me that much.

(for completeness: I have no strong opinion which syntax wins; they all seem about equally imperfect to me)

12 Likes

Then there's the both prefix and postfix alternative:

"Any keyword that operates on an expression may be written before the expression, or after the expression if chained with a dot"


await future

would be equal to:

future.await

match foo {...}

would be equal to:

foo.match {...}
7 Likes

I don’t really agree unless you advocate a prefix syntax. Every postfix syntax will be unfamiliar or worse misleading. The sigil might be surprising at first sight, but not misleading. When you see the sigil for the first time, you realize there is something you don’t know, you google for it, then you know.

All the other syntax look like known pieces of the language, but they are neither field, nor function, nor even really macro.

1 Like

With regard to the previous discussion about yield:

I see yield as a control flow operator like break, continue, and return, with the difference being that it will return () instead of ! within its expression as the function will continue executing from that point.

It definitely will not pass its value back in a (yield foo)? way because of the same reasons that assignment operators can’t - it moves ownership of the value, and it can’t be owned in two places without breaking the model. The only exception occurs if the value is Copy, but Introducing an edge case for Copy values isn’t likely because that would be very confusing, arguably weird and non-orthogonal.

Based on all of this, I don’t think the yield syntax will need to change. It’s perfectly clear and will be familiar to syntax in other languages.

1 Like

FWIW, I’m not much a fan of prefix await?. In addition to being a special-case syntactic sugar, yet still not chain-able, it would also raise the question whether it’d be extensible. I.e., would there be prefix await??, await???, etc.? If yes, that would seem to further complicate that syntactic sugar. If not, it would make await? all the more special.

4 Likes

In all the years of working with async/await in C# and TypeScript, I can count the number of times I wanted to chain awaits on one hand. The problem that “postfix await” syntax tries to solve largely does not exist.

Let’s just stabilize prefix await <expr> with the same precedence as other control flow statements in Rust (i.e. lower than ?).

If futures returning Result turn out to be a frequent occurrence, we can consider adding await? form later. It rhymes with sin2 x === (sin x)2 in math, so it wouldn’t be entirely without precedent.

If people want to chain, but don’t like writing (await (await foo()).bar()).baz(), they can always create a macro and write myawait!(foo() -> bar() -> baz()) or some such.

18 Likes

@vadimcn agree, I think that if you’re chaining awaits, you’re probably doing it wrong. Maybe it’s the way to express some patterns in current stable Rust, but I’d expect futures to have combinators like the ones that Iterator, Option, and Result currently have in std (and the ones worked on in futures-rs crate).

2 Likes

This is a misunderstanding: yield expressions would not pass its value back in (foo in your example). Rather, it would get a parameter passed in from the outer function upon resumption. See., e.g., yield expressions in Python. As such it could have any return type, not just ().

Inasmuch I also see yield and await as very analogous, and would hope they get similar syntax.

3 Likes

Interesting, I wasn’t thinking about it in terms of coroutines, I’m more familiar with the generator / stream concept like you’d see in Python, where it’s completely one way.

Python does have coroutines, the consumer can .send() values back in and those values will be the values of the yield statement.

2 Likes

I don’t understand the aversion to future.await(), which seems like the single best option to me. In terms of abstract analysis, the operation involved is a method call on a future object. It may suspend the calling thread of execution and run other code, but that’s not different than any other synchronous I/O primitive. The generated code bearing no resemblance to a normal method call is an implementation detail. Why do people feel that this particular implementation detail means special syntax should be required?

(The analogy to ? only goes so far: ? can cause the function to return. It really does perform control flow. I don’t think “this function call can block” qualifies as performing control flow.)

9 Likes

If you want to see how some of the proposed await syntaxes would look like in the context of a more substantial piece of code, check out the await-syntax repo. It (presently) contains the source of a rather async-heavy utility from Google’s Fuchsia, translated to several syntaxes from the lang team’s writeup (two prefix and two postfix).

While adapting the code, I’ve noticed that a) as expected, a lot of await calls interact with ?, b) there were not really many opportunites to chain awaits (four out of forty-something calls, no more than two awaits in a chain).

Personally, because of b) above, I didn’t find the prefix syntaxes more awkward, but the one with mandatory delimiters felt more regular, as await? doesn’t have a great solution for . precedence. I must admit that the postfix field syntax flowed rather nicely, and the inconsistency with real field access didn’t bother me at all if I thought about .await as “dot-await operator” instead of “accessing the await field”.

12 Likes

Personally, I would find that appealing. It seems like a net improvement in orthogonality.

It’s a larger pitch, but a good one.

4 Likes

Oh right, I missed that one. Rust kind of has a rule that identifiers and operators use separate symbol sets and whitespace-separated await breaks that rule for basically no other reason than being special, so I’d rule it out as well.

Largely out of curiosity, but also because it might affect the viability of the "prefix" syntaxes: How often do you need to apply ? before await, i.e. you have Option<Future> or Result<Future, Error> or something like that and you need to account for the possibility of not having a Future at all before you can wait for it?