That said, I think a postfixing sigil would have been much better. It seems an important enough thing to denote that a bit of special syntax is called for, and the more unique the better.
var value = (await (await startRequest()).GetBody()).Root;
While the Rust syntax would make this a little more clear, I do not think it is a dealbreaker. var value = startRequest().await.getBody().await.Root; var value = startRequest.await?.getBody().await?.Root; startRequest()
.then(req => req.getBody())
.then(body => { value = body.Root }) var temp1 = await StartRequest();
var temp2 = await temp1.GetBody();
var value = temp2.Root;
which is instantly grokked. fn main() {
let world = gives_string().split(" ").next();
println!("{:?}", world);
}
fn gives_string() -> String {
String::from("hello world")
}
This will fail because the String is temporary, and we're trying to get a reference to it (via split), and so it would be deallocated at the end of the line, being a use-after-free. This, however, compiles: fn main() {
let world = gives_string();
let world = world.split(" ").next();
println!("{:?}", world);
}
fn gives_string() -> String {
String::from("hello world")
}
We're shadowing 'world', but the underlying String now lives to the end of main, so everything works, no more use-after free.I think before I'd want a good real-world example of where doing the multi-line thing goes wrong before I'd want to make an argument that this is why postfix is better.
A comparison: in C++, the behavior of auto-extending the lifetime of const references to temporaries (but not non-const references) is considered a wart in the language design. (Really, you should just assign a temporary to a value because the compiler can elide the copy. : https://abseil.io/tips/101 .)
Tradeoffs, tradeoffs.
However, I also strongly perfer to give these subexpressions names. That tremendously helps when debugging, logging, or just discussing the code with colleagues. In fact, these subexpressions do exist and they are a certain unit to reason about, especially when the program is not doing what it should. In these cases (and not only these) you really want to give them a canonical name and not only "line 42".
Maybe there should be a syntactical equivalent for named subexpressions.
- On the one hand, I agree that "interesting" operations really should be on their own lines. I think I'm going to strongly prefer to keep it to one `await` per line for the foreseeable future.
- On the other hand, I know that many programmers will cram a lot of operations into one line if they can, and when that inevitably happens it would be nice for it to be readable.
- And maybe in the long run, it's possible that a big, mature async ecosystem might make a lot of these operations so commonplace that they aren't "interesting" anymore. Maybe in that world we'll be glad to have a syntax that makes it easy to chain things together.
var value = StartRequest().await.GetBody().await.Root;
Or if you prefer multiline: var value = StartRequest().await.
GetBody().await.
Root;They can still introduce a sigil later, if the .await syntax turns out to be common enough in code that the long keyword hinders readability. This is how it was done with the try!(...) feature, which now uses ? as a sigil. But as the article points out, pure sigils are a scarce resource so .await might well be the best way to go anyway.
I don't think this is necessarily a bad choice overall but I find this particular line of reasoning a bit specious.
(Analogies here suck. Instead, consider that the vast majority of programmers read code that has been syntax highlighted. To completely discount that experience at all would be odd.)
There is a reason that we have three separate lights still instead of one (that is, there's more reasons than just inertia), and that's because some people can't tell the color differences. Specifically, my father can't easily tell the color difference between green and yellow. I've had friends that also had this problem. It's not uncommon.
> To completely discount that experience at all would be odd
I'm not saying to discount it entirely, but consider it in context. I think it's a somewhat elitist argument, because it assumes everyone both has syntax highlighting set up and that their highlighting will work as well as yours.
I accept that syntax highlighting helps, and is a positive if it can be applied well to a solution, I just think it's far from sufficient.
Put another way, an optional, environment specific configuration setting is not sufficient to offset the negative aspects of an official language level feature, if we're able to weigh things prior to being implemented.
If Rust were required (or even officially expected) to be written in an environment where that was always available and easily configurable, I would think different. E.g. if we're talking about Visual Basic, or Smalltalk with it's IDE (IIRC, not that I have experience with it), then I would think different, but as long as the Rust compiler is expected to take in files of text and not some rich format that includes extra metadata, I don't think language design choices should weigh non-text considerations too heavily.
Which is exactly what has been done. So I don't understand what you're complaining about. The rest of your comment is a giant straw man. I've never heard of or seen implied that anyone in any decision making position in the Rust project is "assuming everyone has syntax highlighting set up."
When you make ridiculous assumptions about the decision procedures of other people, then it's easy to derive ridiculous conclusions. But reality is always more nuanced than that: https://news.ycombinator.com/item?id=20031706
My argument at this point (as illustrated by the Rust discussion) is simply:
- Rust/rustc takes in unicode text as source
- Since it has no knowledge at the source level of syntax highlighting, using that to mitigate the downside of a language level syntax for a feature is problematic
- We as the public should keep that in mind when discussing the relative merits of one possible implementation or another of a feature.
That's not denigrating or assuming Rust actually did this, it's a note about the community level discussion and how some people approached it, as evidenced by a very specific comment in this thread, and how I thought it had some problems when applied to language level decisions.
I guess I'm just so tired of folks piping up with the "not everyone uses syntax highlighting" crap almost every single time anyone even hints at the notion that syntax highlighting can help a particular piece of syntax. I guess you're probably tired of the opposite.
Which was in response to someone's criticism regarding .await notation that "it's easy to not even be aware it exists and think a struct had a member called await instead..."
> Now if someone said, "it's impossible to read the await keyword due to its placement, but since everyone uses syntax highlighting, it will be okay since it will be colored differently,"
That's how I interpreted it based on it being a reply to that exact criticism.
> I guess I'm just so tired of folks piping up with the "not everyone uses syntax highlighting" crap almost every single time anyone even hints at the notion that syntax highlighting can help a particular piece of syntax. I guess you're probably tired of the opposite.
I understand! As I noted earlier, I'm for syntax highlighting in general. If I was forced to never use syntax highlighting for programming again, I might consider a career change and only programming on things I really care about instead of to pay the bills. It's because of this extreme distaste for how annoying it is without syntax highlighting that I'm extremely adverse to making it any worse than it already is, because there's been a few times where I've been forced to endure it.
For the record, I'm fine with the currently accepted await syntax. Out of what I would consider the ideal outcome to me (postfixing special sigil/character), it's at least postfixed. While prefixing await looks prettier in the singular case, it makes any sort of chaining cumbersome and error prone to parse out by eye, and as I've gone to pains to represent here, when it comes to functionality and prettiness, I error heavily on the side of functionality (where functionality includes safety and a premium on not making complex things harder to deal with than they need to be). My comments are really a tangent on the submission topic and not meant to be applied directly towards the specific solution Rust has gone with (there's a reason I waited until quite deep in the thread to use it specifically as an example). That is, .await is a perfectly acceptable outcome in my eyes without the need to justify it through syntax highlighting.
And we aren't in the late 70's anymore, where languages had design decisions (keywords always caps) due to printing limitations.
Yes, and if we outlawed the use of cars without traction control on certain roads, then I would be fine with designing with the assumption traction control will be on. But as long as you allow older cars without traction control to still drive on the road, you are making the roads less safe for a certain percentage or class of drivers. I view that as different that just making the road safer for some.
> But we should also keep an awareness that it won't always be there.
Exactly. And this is why I think it doesn't make sense as a mitigation to a language level feature which will always be there.
Specifically, I think syntax highlighting is a useful feature and a plus for a comparison, I just don't think it works as a mitigation for a negative for something that pervasive.
All other things being equal, syntax highlighting abilities might push me towards one solution over another, but they won't make me ignore a problem I perceive with a solution.
A nitpick:
> But as long as you allow older cars without traction control to still drive on the road, you are making the roads less safe for a certain percentage or class of drivers.
Probably. But if a sufficient fraction of cars have traction control, and we sufficiently improve the performance of traction controlled tires at sufficiently small damage to the performance of other tires, the reduction in the threat of being hit by cars with traction control will outweigh the increase in risk of losing control for those cars without, even before trading off risk between cars (which is certainly more complicated).
I don't think there's a direct mapping back to syntax and highlighting.
That one. :)
> But if a sufficient fraction of cars have traction control, and we sufficiently improve the performance of traction controlled tires at sufficiently small damage to the performance of other tires
That's a slightly different scenario, where the change is to increase safety. Then it is, as you say, a trade-off. It could just as easily be a trade-off because material is cheaper though, in which case different calculations are important.
Also, it's important to be careful not to exclude important information from the calculation even if it's about a relative safety increase. For example, if you increase the safety for 90% of the people but decrease it for 10%, and that 10% is mostly comprised a population that is unable to switch and benefit (e.g. poor people with little choice in the vehicle they drive, as they drive what's available and cheap), you might not only be forcing that risk on an already captive population, but they might also be a population that is resistant to change that would mitigate this danger (they can't afford new cars and they can't afford better cars). Conversely, shifting risk to the wealthier (more elastic) part of the market might yield more of a net reduction in risk as they are capable and willing to respond to the increased risk (or more likely, increased annoyance).
> I don't think there's a direct mapping back to syntax and highlighting.
There isn't, we're getting into the weeds, but conceptually I think there are some interesting points that apply at least partially. For example, not all changes are felt and valued by the populations they affect similarly. E.g. not relying on syntax highlighting for a language feature does not affect a person that uses syntax highlighting that much one way or another (it will likely still be highlighted in some way), but it can impact those that don't or can't fairly heavily. If I'm stuck trying to review some bug while on vacation through some crappy online git interface that isn't highlighting code well, or maybe even at all, I'm going to curse the heavens if there's a feature that's easily missed or hard to follow in monochromatic output if it's causing me problems in my stressful moment.
Perhaps that's my sysadmin history coming through, but I want stuff to be simple, reproducible, and obvious. Complexity, fragility and obscurity are the enemy, and I fight them wherever I see them. ;)
EDIT: nvm. it says in TFA not the linked writeup
instead of (await doSomething()).somethingElse()
doSomething()@await.somethingElse()
keyword EXPR MAYBEBODY <=> EXPR.keyword MAYBEBODY
> “Dot keyword:” In the previous post, a sketch was made of an idea in which certain keywords could be postfixed or infixed using the dot operator. This idea is only a sketch, and is not implied or guaranteed by the decision we’ve made here.
`foo.await!()` would just expand to `(await foo())`, and anyone could write their own `my_await!` macro that works similarly.
IMO, if the goal were ultimate language consistency then that would involve both adding an `await` keyword (with mandatory braces, like `if` and the others) and a postfix macro for the cases where it looks nicer. However, when considered by itself, there's no denying that `foo.await?.bar` appears nicer than `foo.await!()?.bar`, especially if there is no guarantee that postfix macros will ever become a thing.
(Though in honesty I think all these proposals are just trending towards justifying an F#-style pipeline operator.)
Interesting point, thank you. I hadn't seen this previously mentioned, and it's definitely a reasonable argument.
> there's no denying that `foo.await?.bar` appears nicer than `foo.await!()?.bar`, especially if there is no guarantee that postfix macros will ever become a thing.
Agreed, for sure. I'm honestly just very concerned about these features because I see them as stepping stones to others. The path from postfix macros seems much brigher than the path from postfix keywords.
I think I agree with you about an await block being a good idea.
Thanks for the response, I think this is the first meaningful response to the prefix await + postfix macro that I've read.
It would also appear to be deficient for the same parsing reason I mentioned, i.e. that you need some way to tell whether `2 + 2.bar!()` should expand to `2 + bar!(2)` or `bar!(2 + 2)`; the RFC appears to choose the latter, whereas a hypothetical `await!()` would want the former. This problem is called out in the RFC:
"Rather than this minimal approach, we could define a full postfix macro system that allows processing the preceding expression without evaluation. This would require specifying how much of the preceding expression to process unevaluated, including chains of such macros. Furthermore, unlike existing macros, which wrap around the expression whose evaluation they modify, if a postfix macro could arbitrarily control the evaluation of the method chain it postfixed, such a macro could change the interpretation of an arbitrarily long expression that it appears at the end of, which has the potential to create significantly more confusion when reading the code."
2 + {
let _self = 2;
bar!(_self)
}Similarly, postfix keywords are not a thing that exist in rust, and await is the only accepted one.
Could await be implemented as a prefix, with an additional macro?
awaits!(foo()) that expands to (await foo())
"{} + {}".format!("foo", "bar")
and other niceties. It would have also solved the try macro's issues, like: result.try!()
etc. There are plenty of use cases where postfix macros are awesome.Macros are already a known control flow mechanism, so a postfix macro should be very easy for both new and old rust users to understand.
It isn't a fake field access, so it's already a step ahead of the competition.
And you get prefix await, which every other language uses, and this addresses the singular argument against macros being used (which is that the macro could not be implemented by users), though I never felt that it was a strong argument anyway.
I don't want to go back and forth discussing it. It isn't happening. I was just curious if the prefix await would have addressed that one argument.
"{} + {}".format!("foo", "bar")
to be better than format!("{} + {}", "foo", "bar")
Similarly with `result.try!()`, that does not look as nice to me as `result?`.And postfix-macro syntax for await! would lead to code like
let result = sendRequest().await!()?.getBody().await!()?.Root;
or if we used this for `try!()` as well then it would be let result = sendRequest().await!().try!().getBody().await!().try!().Root;
which just looks very noisy to me.I do, especially in terms of writing. It's quite annoying to 'wrap' things, in my opinion. This is why chaining is desired to begin with - people consistently prefer to append new code than to wrap.
> Similarly with `result.try!()`, that does not look as nice to me as `result?`.
I also prefer ?. 'try' is so common it's worth optimizing down to a single character.
My point is to compare to the prefix try, not the question mark, as a motivator for where postfix macros are a reasonable concept.
> let result = sendRequest().await!()?.getBody().await!()?.Root;
It's an additional 3 character per 'await' vs the other syntax, which I think is fine - a small price to pay for a syntax that makes sense. If await were so common, I would once against think a sigil is the way to go, but I don't believe that await justifies that level of optimization at this point.
One thing I strongly disagree with - - this article said other notations would require Rust users having to learn more notation. This still requires learning more notation, just it's easy to not even be aware it exists and think a struct had a member called await instead...