I agree
Unless you have all-caps fields/methods.
I agree
Unless you have all-caps fields/methods.
Thanks for that. Would it still be a breaking change if .ALLCAPS
(with proceeding DOT) was special cased only, or even if just .AWAIT
is special cased only in the 2018 edition and in async
fns/blocks? Perhaps I’m forgetting of some current use case, but I don’t think there is any current use of that, is there? I believe its written via Type::ALLCAPS
for constants including associated constants.
If however, none of that works, and possibly in any case, my recommendation remains to change the sigil to one of: #
@
¡
.
Note: I’ve taken some heat for the postbang ¡
sigil suggestion, and while I mostly accept the accessibility arguments against it, I leave it on the list because of various other advantages, including: parity weirdness with this very weird language feature—“postfix unary keyword/named operators”.
¡Keep Rust weird!™
I can't think of a case where all-cap fields or methods wouldn't be considered exceptional. Its even linted against. Thus a syntax collision with it would not be a failure of rust language/syntax design. We'd live with that, I think, couldn't we?
Hey, you guys know that you’re trying to archive three things at same time, right?
await
await
syntax to be more friendly for ?
await
syntax to be more friendly to chaining.It’s kind of over greedy, you know. Maybe things will be easier when given up on either point 2 or 3?
I mean, if you only want to have 1 and 2, you can have:
let result = (await task)?
let result2 = (await result)?
or maybe
let result = task await?
let result2 = result await?
If you want to have 1 and 3, you can have
let result = task.await.another_operation().await.yet_another_operation()
Both of them looks nice and clean.
But with all 1, 2, 3:
let result = task.await?.another_operation()?.yet_another_operation()
It just look a bit weird anyhow.
This certainly looks clean, but it shouldn't be that clean. This particular code used 2 await
s, which might slow down the program significantly. I want await
keyword to be noticeable from the orbit.
Here are the design proofs of these latest alternatives. If anyone remains interested or is undecided, I would strongly encourage you to clone this repo and play with the source in your favorite editor, including modifying highlighting for the .AWAIT
as a keyword.
future.AWAIT
I remain of the opinion that upcase .AWAIT
fixes both the recognition and field access confusion problems, it doesn’t require unusual-whitespace/comments or changing the sigil. To continue with the design-is-analog intuition, we could say this is just a very slight move to the right vs the status quo.
/*magic «*/.await/*»*/
comments workaroundAnd just for completeness, my best workaround for the status quo. I sincerely hope it doesn’t come to this.
Thanks, as always, for your patience and consideration.
This is a breaking change, because AWAIT
isn't a keyword, so struct Foo { AWAIT: u32 }
is legal (though non-idiomatic) code.
As I had asked @gbutler above: if it was only applied (as a special keyword and postfix unary operator) in an async block or fn (which do not exist in stable, yet) then it shouldn’t be able to break anything. Or is my logic wrong?
Or is it just that doing that would be particularly horrible from an implementation perspective in the compiler?
As usual, macros make everything awkward. What does foo!(x.AWAIT)
mean if foo
is a macro that takes an expr
?
There's probably a way to make it work, but things like that are why I prefer it just being a keyword: that way it works the same everywhere, and it makes it easier to give targeted errors about its misuse.
I won’t suggest the .AWAIT
alternative isn’t a complication, and I apologize for that, but I think its a meaningful and worthy alternative. Some strategies for easing the change in with no, or very minimal breakage:
AWAIT
outside of async
blocks/fn’s, gets a warning level lint, saying that it will become a keyword soon (next edition or 3 releases from now).AWAIT
is in scope, if you will, only in async
blocks/fn’s.async
blocks/fn’s. (Or am I not understanding the problem you raise?)Actually, I’m really way out of my depth at this point and should let you and other compiler developers decide if its reasonable to implement.
What if you have a field AWAIT that you want to access in a async context. (I know this is contrived, but thats what backwards compatibility means)
In that case you are forced to use x.r#AWAIT
as part of re-writing to use the async
block/fn for the first time, or on nightly, when the change goes into effect. That makes it sort-of breaking, in some abstract sense, but keep in mind that no such stable code exists today, because there are no stable async
blocks/fns, correct?
The "next edition" path is what we did to make await
a keyword
Don't worry about commenting; I'm not on the compiler team either!
I do bet they could figure out a way to make it work; after all you can pass struct
to a macro that wants an ident
and use it as a keyword, so maybe there's not actually an implementation problem. I just like avoiding contextual keywords in expressions because it avoids strange edge cases that need to get defined.
may_23_meeting.await
Interestingly, that I’ve found the following syntax more visually balanced:
let n = save_file(field).await {}?;
with scope
and without
However, I don’t see any use case for this introduced scope. Maybe someone has any suggestion?
Why is it so important to highlight .await
over everything else?
There a lot of things that may slow down the program (mutexes, blocking I/O, allocating a lot of memory, etc). All of this can be hidden in inner functions, and we don't have a special syntax for it.
In some languages (like Go) the await operation is implicit, and people have no problems with that.
Let's see:
.lock().unwrap()
and other functions/methods to use a mutex, it is very noisy, it isn't implicit.::new()
, I don't think these are unnoticeable.You think that .await
is a non-special field access?
Go isn't Rust, its goal is different. While both aim to provide safe concurrency, only Rust aims to achieve zero-cost abstraction and try to compete with the likes of C and C++.
If that isn't enough to convince you, C++ clones object by default while Rust requires explicit .clone()
.
You skipped the All of this can be hidden in inner functions in all quotes, which is the important key of my point.
In a code like:
let data = input.get_data();
We don’t know what is happening in get_data
. We have to check either its documentation or its implementation. And, IMO, this is a reasonable trade-off. We can’t expect to be explicit about everything all the time and everywhere.
The reason why it is important in asynchronous functions that the points where await
is used are noticable is because it is the caller’s duty to determine when to await. When calling a method that takes some time to compute, you don’t have a choice in whether you wait it out or not, because you have to.
In the experience report from a few days ago, the situation surfaced where the futurres were awaited in the wrong order because typing .await
unconsciously is seemingly too easy and does not lead to putting enough thought into where to use it. Asynchronicity is best applied to tasks that can be worked on in parallel, but in order to do so, one needs to consider which tasks can be parallelized and write their code in accordance. Thus, a more noisy approach than a simple .await
may have some merit.
I am not going to voice my preference on either of the many suggestions so far, but I feel like this point has been overlooked since we’ve heard the first experience report.
input.get_data()
(method call) tells me that "the program is going to perform an action, and that action may be expensive", input.data
(field access) on the other hand tell me that "hey, this costs you nothing, do it as much as you want". As a result, I tend to assign input.get_data()
to a data
variable but leave input.data
as is.
This is how I handle input.get_data()
:
let data = input.get_data(); // this is a method call, which might be expensive, therefore I must assign it to a variable if I want to use it multiple times
useItOnce(&data);
useItTwice(&data);
On the other hand, this is how I handle input.data
:
useItOnce(&input.data);
useItTwice(&input.data);
Unfortunately, future.await
looks like a field access, which tells me "I cost you nothing, use me as much as you want". It lied.
So to answer you: Although we do not know what is happening in .get_data()
, we know for sure that it is not zero-cost, therefore we tend to invoke such calls as little as possible. Field access on the other hand is implied to be zero-cost, so for readability’s sake, we usually don't assign them to a variable.