A final proposal for await syntax

You said this as if making await more visible is an impossible task.

2 Likes

This is just a misunderstanding on your part.

input.data is not pure or zero-cost and may actually be expensive.

This has already been explained multiple times (I'm sure I missed some in the quotes below) and I don't understand why you ignore it.

9 Likes

And just to drive this point home:

I've seen code that locked a mutex on Deref::deref. I don't remember the exact semantics nor where it came from, sadly, but at least one person has thought it useful to create a type to wrap a mutex silently on Deref.

6 Likes

Why do that though. Things like .unwrap() or the like are about as visible as .await and yet they do even more serious stuff than .await . It doesn't seem to me that .await does anything terribly 'graver' than what function calls and field access may do (and in fact in some cases do do).

7 Likes

What is missed in this form of argument is...

Yes of course, I can create a fn called noop() in user code that calls panic! 1 out of 41 random times and spins in a loop for 1 to 600 seconds the other times. But we wouldn't call this a design flaw of Rust the language or libstd, that I could do that, would we? It would just be my design mistake to name the function that, if what it does is intentional, or my bugs if the panic and sleeps are unintentional.

I believe @KSXGitHub (and many others) have been making a valid argument regarding the surface syntax of async/await that end users will need to learn and interact with. I think the argument is based on very general and well accepted principals of API and language design. Relatively slow operations (e.g clone()), complex, dangerous things (e.g. panic!, unsafe {}), and control flow should be explicit and clearly distinguishable from the other operations like side-effect free field reads, and not hidden from plain site.

That a user could abuse Deref is not itself a Rust design flaw as best I can tell, or if it is its orthogonal. DerefMove doesn't exist. That a user could abuse Deref is also not a compelling argument for why the surface syntax should be future.await. The increasingly obvious problem right under our noses, from which these arguments tend only to distract, is that the future.await syntax is both easily confused with field access, and hard to spot in complex chains, even if you understand what it does. And because the surface syntax for the await operation is designed and implemented in the rust compiler, it would be Rust's own design flaw .

Compounding this is a plan to stabilize the new syntax on a fast track, before there is much, if any, non-toy code that actually uses it, and before we have any time to see how learning it goes for new users. After it stabilizes it will be much harder to fix. This is the unfortunate result of deferring the syntax decision for so long. The claim that its just syntax and not important obviously hasn't been a productive approach here. Do you find that users don't think it important? Read this and other threads? To date Rust has done a very good and careful job with syntax, so this is particularly surprising to me. What I think made it possible for this to happen, ironically, is that the place holder await!(future) regular macro syntax is actually pretty reasonable and without these major flaws. Several users have suggested they would just wrap future.await in their own macro.

Fortunately, there are many better syntax designs, some proposed and batted around for over a year, some suggested for the first time on this thread: future.AWAIT, await!(future), future@wait, future#await. Pick one of these better designs, and lets make sure to test the experience with non-toy code, by both new and veteran users, before stabilizing it.

11 Likes

I have not seen any serious claims of that. On the contrary, it's quite important to get right, and quite hard to get right.

"better" is a very large claim, and one that isn't supported by "here are the advantages". It also requires careful consideration of the values and requirements that people hold, including those not satisfied by those proposals. People have fundamental disagreements about which things they value most strongly, and different weights on those values will produce different outcomes. It's easy to dismiss alternatives because you don't value the same things as the proposer; it's much harder to carefully consider and weigh what many different people care about.

19 Likes

Given that it has come up several times, and that I discussed it in some of these threads myself, I think this is worth mentioning explicitly in this thread:

.keyword syntax for keywords other than await (for instance, .match) will not happen without a separate RFC, separate discussion, and separate rationale. .await would serve as input into that, but not as ironclad “we should definitely do this” precedent. In particular, I’m treating the decision on .await and the decision on .keyword as orthogonal.

I hope that helps disentangle the two concepts somewhat.

Apart from that, I’d like to observe an issue that applies to this kind of discussion, when everyone is arguing from closely-held values and requirements that conflict. I feel that many, many people are talking past each other here. Responding to “X has advantage Y” with “but A has advantage B” doesn’t do anything to establish common ground, and when Y and B involve closely held values that can create a great deal of acrimony and frustration. Such responses make it feel like the person responding and proposing the new solution doesn’t care about (or isn’t considering) the values previously expressed.

In any discussion like this (and I doubt this will be the last feature prompting widespread debate), I think it’s worth explicitly acknowledging that people care about different things, that we’re in the domain of satisficing solutions, that it’s unlikely any proposed solution is going to make everyone happy, and that at best we’re looking for (and not finding) solutions that better satisfice everyone. In that vein, anyone proposing additional solutions, or arguing for one solution over another, should be looking not just at advantages, but also how well any given solution satisfices all the values and requirements in play here, not just one’s own values and requirements.

9 Likes

Now I know what I feel so wrong about the idea of trying to stabalize .await as fast as possible. I had tried to describe it clearly in some of my comments but people seem to ignore it. Thank you for pointing that out.

2 Likes

If generic .keyword is accepted, it would be a deviant syntax with no benefit. But if it is not, .await would be an exception, which is bad for consistency. So it is bad either way.

That would be great if people (including me) can actually do that. But we are mere mortals. Personally, I argue not to convince my opposing side, but to present my arguments to the Rust team, for they are the ones who make the ultimate decision.

2 Likes

I think everything that needs to be said about this has been said, and it just comes down to preferences. My preference is await <expression>, because it results in a unified syntax. I think chainability is best solved with an alternative sigil that would work for all expression-returning keywords (e.g. <expression> ~ await, <expression> ~ match { ... }). I suppose this has already been considered and thrown out by now.

If the consensus is .await then I’ll accept that. I am curious as to how support is measured. Is it based off voting?

2 Likes

If this is actually the case then the possibility of .match should not be used as an argument for .await, as it was in the original post here. I've said this before in this thread, but I really want to avoid a scenario where we continually add new bits of one-off syntax and never unify any of them.

(await foo)?, await(foo)?, await!(foo)?, and foo.await()? all lean on the existing and future semantics of the syntax they reuse, which is what I mean when I talk about unifying syntax. foo.await, on the other hand, is a rather large divergence from that syntax's existing semantics, and if there's the slightest chance that it will wind up as the lone instance of "field syntax but it's not a place expression" then that's a massive, massive downside IMO.

14 Likes

We’re mortals too. :slight_smile:

That is very much the kind of reasoning we’re trying to do, though: we’re very much trying to take all the underlying values into account, and deal with the fact that they conflict. And I imagine we’ll be spending a lot of time explaining how we’ve done so.

Part of my point is that arguments carry much more weight when they include at least some of that kind of analysis.

2 Likes

I just want to insert, since it’s seen some actual discussion, that fut.AWAIT is strictly worse than fut.await from the language grammar position.

This is because await is a reserved keyword, and AWAIT is not. Even if we were to break our stability promises and reserve AWAIT as a keyword mid-edition, it would then be an exception to the fact that every other keyword is all lowercase, one word.

It would be inconsistent to say that fut.await breaks some consistency that fut.AWAIT doesn’t.

9 Likes

I claim that at the very least, it's implicit in the decision to defer on the syntax question until late in the overall async/await development process.

On the contrary, it’s quite important to get right, and quite hard to get right. “Better” is a very large claim, and one that isn’t supported by “here are the advantages”. It also requires careful consideration of the values and requirements that people hold, including those not satisfied by those proposals.

That resonates with me and relates to my prior comments on design-is-analog. I also think, unfortunately, the process taken to date, or at least this last part of it that I've been apart of, has uniquely thwarted that kind of value-based analog-weighted consideration. I'd consider elaborating on that, but only later.

1 Like

The discussion about whether syntax is important is pretty off-topic, but I think it's worth addressing briefly:

On the contrary, the desire to make a good syntax decision is why that aspect of the design was not locked down prior to implementing the feature in nightly. That's why the macro version exists. Yes, the discussions around syntax seem not to have been noticed by large parts of the community until this "final proposal" post was made, which is quite unfortunate. But those discussions did happen, because everyone involved does consider syntax important.

6 Likes

Please note my above comments on the breaking change aspect. I theorize (awaiting your more detailed analysis) that because async blocks are currently unstable, an AWAIT keyword can be gracefully introduced with zero immediate breakage to currently stable code. FWIW: I also concede the point that it is at least a complication, and we all will eventually want it to be a reserved keyword, so a new edition or a waver will eventually be required.

With all due respect, this last part goes binary, equating two completely different sets of downsides on a binary scale, e.g -1 == -1. From a user perspective, IMO, we are already in very new territory with any form of postfix unary named/keyword operator. I'm not sure users are particularly worried about rust introducing its first upper case keyword. On other side, with .await users have and will be frequently bothered about operator visibility and looks-like-a-field-access. From a lang. grammar perspective, I imagine, this is a new class of KEYWORD, that we didn't have before, and that unfortunately, AWAIT wasn't also reserved in 2018.

Okay, I'll accept that. Certainly anyone surviving this long in this thread, cares about syntax!

1 Like

People are and will be bothered by the existence of str. That doesn't mean it is the wrong syntax. You can't assume these things are unlearnable/unacceptable just because there's a potential for some minor confusion or need for explanation.

Moreover, I don't see anything wrong with looking at this as field access. Field access is what field access does, and if this looks like accessing await field on a future object, then that is a fairly natural and consistent model for reducing the abstraction. The whole point of abstraction is to hide details, and if you're curious about what is going on underneath, you are always expected to reform your understanding. Abstractions don't provide clarity on internal details. They expose a framework for achieving useful results even if all you have is an incorrect/simplified understanding of the system.

6 Likes

You lost my there, no, sorry. I think its pretty unnatural. In fact I think it is the most unnatural of all alternatives being seriously considered. That something that should be side-effect free has side effects, including control flow. await is a verb and its more like a unary function, macro, or operator.

5 Likes

I think the most important considerations are (1) that postfix form be used, for the already outlined reasons, and (2) that any superflous symbols or text be omitted, because this is going to be a core language feature.

You disagree. I disagree. It doesn’t really matter what we disagree about if we’re just slinging opinions. We agree that humans can learn, and that using a language means understanding its core features, and that if you care about side effects, you should know what parts of your language cause them. These things are not a function of the syntax. The concern you raise is that people will see .await, not know what it means, assume it is a field access, and then fail to understand what the code does. This will happen no matter what await syntax is used, because it can only be used in a specific context. They will always have to learn what await looks like.

16 Likes

I think the larger concern is that people will see .await assume it is a field access and move on not noticing that this is a sync point and NOT a field access. Now they have the cognitive burden of looking at every field access and determining if it is going to be an await or not. When C# introduced await they made sure that it stood out to a user casually reading the code. With postfix field access await the person skimming the code has a much higher chance of missing a critical piece of information.

8 Likes