Wow, I thought this was a joke too. Don’t do this, note this will not work for us with US keyboards, we don’t have US international layouts in the US generally without requiring the user to configure something special IE physically changing the layout of our keyboards. None of the suggestions worked for me that would be simple, a lot of keyboards in the US don’t have altgr (which is often NOT the right alt key) and on laptops its even worse. Alt + 1 doesn’t work on a standard US keyboard on either side, alt + ctrl doesn’t work for either side, and often corresponds to odd shortcuts. Indeed the only way I was able to type ¡ was with ALT + 173 only on the numpad (or using another number code).
Note I have a full five US layout keyboards all different where there was no other way to type out ¡ with out ALT + 173 only on the numpad, on linux It gave even stranger results when I tried to do ALT + 1 (not a random symbol, but printed (arg:1) in terminal [didn’t matter the side that alt was on], and in IDEs it selected weird things).
It is also available using a standard US keyboard by switching to the US-International keyboard layout.
READ THIS! and please stop suggesting this! Basically anything that isn’t just shown on a standard US keyboard is not going to be easy to type for standard US keyboards, and as you’ve seen its even worse for non US keyboard layouts.
Okay, so I’m a typography nerd and just like how it looks. You’ve outed me. But this topic is about language design and syntax.
If ASCII is essential, the main arguments remain for @await, @wait, or #await as a strict improvement over the current .await, which will be confused as field access.
With my language team hat on: I find this argument really compelling and worth exploring more.
What would code that awaits multiple futures in parallel (rather than in series) typically look like?
If we want to make it easy to await multiple futures in parallel (and in particular, not await a future until the function can’t proceed without the result), what syntax could/should we provide to do that?
EDIT: just caught up with the rest of the thread, and read the notes about “defer”. That seems to largely address the issue, and I like that there’s enough room to experiment with higher-level structures in the crate ecosystem.
In particular, I personally would like a combinator like join or something that works like so:
let ((a, b), c) = future_a
.join(future_b)
.join(future_c)
.await;
You could trivially build this on the join function. The nested tuples aren’t ideal of course, but it scales to arbitrary numbers. But that’s I guess more of a “futures library” discussion than anything.
So I just saw a tweet that shows a piece of code that uses .await today and I find that that .await part is not only barely visible, it is also completely indistinguishable from method calls. And this is from a code block that has syntax highlighting.
One of the impressions I have about Rust is that it forces you to explicitly perform operation that are expensive and/or time-consuming (for instance, you must explicitly call .clone() to clone an object, unlike C++). And in my opinion, that is a good thing.
Regarding await, while I do not consider it expensive, it can potentially be time-consuming (the program have to wait for the future). Having an await operator that is indistinguishable from field access might encourage programmer to write code that unnecessarily awaits independent futures sequentially:
get_future_1().await + get_future_2().await
while the correct (more performant) code should be:
let (a, b) = get_future_1().join(get_future_2()).await;
a + b
I don't know how can Rust make the "correct (more performant) code" easier to write and read, but I think Rust should make the "wrong code" more explicit by making await operator more visible. So that whenever I use await twice or more, the source code will scream to my face that "Hey, you have 2 awaits! Care to join them together?".
Ironically, this also rules out postfix sigil (one that I supported) as it is even more subtle than dot-await Actually, I take that back, because since I find ? postfix quite noticeable, I think @ postfix must be visible as well
We had discussed whether it should “auto-deref” or not. At present it does not and it’s not clear that one would ever want it to – in particular, it consumes the thing being awaited, and that would only work for Box<T>, which implements future anyway. But it’d be nice to retain the option in the future (I expect the . operator to generally “auto-deref”).
Methods currently error on an uninferred type variable, it’s true, but that’s largely an implementation defect. If we improve rustc’s inference algorithm, I think I would prefer for type-checking to continue and only error if we indeed cannot figure out what method is being invoked. In general, we do not insert an auto-deref unless there is no possible resolution for the method at a given level.
So, if we applied the same logic to futures, it ought to be forwards compatible with our current behavior. Basically, all cases where an auto-deref might’ve been an option would currently error, so we could add it later (and, because we have no DerefMove-like trait, I don’t believe any such cases presently exist, but I could be overlooking something and I’d like to know if so). =)
That looks like syntax highlighting that hasn’t been updated to know about the await (or async) keyword. await should use the same color as let and fn and use do.
This is not about auto-deref. This is about type inference. In the code example, the generic argument X for Foo<X> needs to be inferred. Only Foo<u8> implements Future and thus allows application of .await. In the current version, this type argument is thus inferred to u8 and .await can be applied. The question is then if we can rely on this behaviour and what is done to ensure that this will stay consistent across language versions. Specifically since the reason the current code enables this is the hidden, eventual call to poll_with_tls (or something similar) is called directly on the argument of the defacto-macro, and it requires impl Future.
I see, thanks for clarifying. That situation is actually simpler than what I thought you were asking about. The short answer is that, for better or worse, we do the same sort of deduction for methods, as you can see in this example here:
If we ever adopt the fields-in-traits RFC (as I hope we do at some point), I presume we would use the same basic logic there too.
At present, the compiler is sometimes conservative when Self is fully unknown, but it is willing to infer other trait parameters on the basis of the set of existing impls. In this case, X is unknown in the trait, but Self is at least partially known, and that’s enough to narrow things down to 1 impl.
It shouldn’t have required syntax highlighting in the first place. I want to read a raw piece of source code on my browser, Notepad, git diff, or via cat without overlooking a crucial piece of information.
Relying on syntax highlighting is a huge red flag for me. I should be able to open up rust code in something like notepad or cat from a terminal and quickly be able to see what’s going on.
But what about if you want to join 4 futures, how about 5 or 6? This could go on an on, and without variadic generics, we can’t do any better than a macro or nested tuples. Now a macro could easily convert from nested tuples to the flattened version, and we can do that now.
In C#, I yesterday (literally – I just checked) gave the “you should do that in parallel” feedback for this code (domain object names obfuscated, but the code is otherwise untouched):
var sprockets = await this.ExtractContent<SprocketContent>(SprocketPath);
var widgets = await this.ExtractContent<WidgetContent>(WidgetPath);
var spanners = await this.ExtractContent<SpannerContent>(SpannerPath);
So I think prefix await isn’t immune to “just await without thinking about it” either.
That said, it’s a pain in C# to do this differently, because Task.WhenAll only supports the homogeneous case. If I understand How will Promise.all be implemented? correctly, then this will be way better in Rust – whether prefix or postfix – as it could be
let (sprockets, widgets, spanners) = join!(
self.extract_content::<SprocketContent>(SPROCKET_PATH),
self.extract_content::<WidgetContent>(WIDGET_PATH),
self.extract_content::<SpannerContent>(SPANNER_PATH),
).await;
+1, and I wonder if that actually makes .awaitless noticeable than with no highlighting, as one’s brain starts relying on the colours for mental parsing shortcuts.
I am suprised that no one is talking about UCS macro in this thread. I’ve noticed that a large part of community in reddit has reached consensus on postfix macro fashion powered by unified call syntax + prefix madatory.
Besides, there is a unofficial survey, which also shows favors of postfix macro among all proposals (the one with biggest proportion of ‘perfect’ option).
In brief, with UCS-macro, postfix await will be not magical; it’ll be trival, orthogonal, user definable (except that await is a keyword). And the most importantly, it completes the chaining story in rust so that we don’t need ? for Try, magical await field access for async and new magical syntax for something may appear in the future. (ps: ? syntax is cute, I love it)
It doesn’t require syntax highlighting. And if you don’t have syntax
highlighting at all, I think it still reads just fine.
Having some syntax highlighting that is incomplete (and doesn’t know
about all keywords) is harmful: highlighting some keywords gives a
signal that keywords will be highlighted, making un-highlighted keywords
easier to miss.
People aren’t talking about it because it’s already been discussed to death (months ago), and rejected by the Rust team (for await). Perhaps postfix macros will be implemented in the future, but not right now.