Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upResolve `await` syntax #57640
Comments
Centril
added
A-async-await
C-tracking-issue
T-lang
labels
Jan 15, 2019
This comment has been minimized.
This comment has been minimized.
|
I thought it might be useful to write up how other languages handle an await construct. Kotlinval result = task.await()C#var result = await task;F#let! result = task()Scalaval result = Await.result(task, timeout)Pythonresult = await taskJavaScriptlet result = await task;C++ (Coroutines TR)auto result = co_await task;Hack$result = await task;Dart
With all that, let's remember that Rust expressions can result in several chained methods. Most languages tend to not do that. |
This comment has been minimized.
This comment has been minimized.
I'd say that languages that support extension methods tend to have them. These would include Rust, Kotlin, C# (e.g. method-syntax LINQ and various builders) and F#, although the latter heavily uses the pipe operator for the same effect. |
This comment has been minimized.
This comment has been minimized.
|
Purely anecdotal on my part but I regularly run in to dozen+ method chained expressions in Rust code in the wild and it reads and runs fine. I haven't experienced this elsewhere. |
This comment has been minimized.
This comment has been minimized.
earthengine
commented
Jan 15, 2019
|
I would like to see that this issue was refered in the top post of #50547 (beside the check box "Final syntax for await."). |
This comment has been minimized.
This comment has been minimized.
kvinwang
commented
Jan 16, 2019
•
Kotlin's syntax is: val result = doTask()The |
This comment has been minimized.
This comment has been minimized.
|
Thank you for mentioning that. Kotlin feels more implicit because futures are eager by default. It's still however a common pattern in a deferred block to use that method to wait on other deferred blocks. I've certainly done it several times. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
maybe you should add both use cases with a bit of context/description. Also what's with other langs using implicit awaits, like go-lang? |
This comment has been minimized.
This comment has been minimized.
HeroicKatora
commented
Jan 16, 2019
•
|
One reason to be in favour of a post-fix syntax is that from the callers perspective, an await behaves a lot like a function call: You relinquish flow control and when you get it back a result is waiting on your stack. In any case, I'd prefer a syntax that embraces the function-like behaviour by containing function-paranthesis. And there are good reasons to want to split construction of coroutines from their first execution so that this behaviour is consistent between sync and async blocks. But while the implicit coroutine style has been debated, and I'm on the side of explicitness, could calling a coroutine not be explicit enough? This probably works best when the coroutine is not directly used where constructed (or could work with streams). In essence, in contrast to a normal call we expect a coroutine to take longer than necessary in a more relaxed evaluation order. And So, after hopefully having provided a somewhat new take on why post-fix could be preferred, a humble proposal for syntax:
Adapting a fairly popular example from other thread (assuming the
And with the alternative:
To avoid unreadable code and to help parsing, only allow spaces after the question mark, not between it and the open paran. So The interaction with other post-fix operators (such as the current
Or
A few reasons to like this:
And reasons not to like this:
|
This comment has been minimized.
This comment has been minimized.
There's nothing special about So how would that work for Futures which don't return |
This comment has been minimized.
This comment has been minimized.
HeroicKatora
commented
Jan 17, 2019
•
|
It seems it was not clear what I meant. |
This comment has been minimized.
This comment has been minimized.
Thanks for clarifying. In that case I'm definitely not a fan of it. That means that calling a function which returns a It's very syntax-heavy, and it uses |
This comment has been minimized.
This comment has been minimized.
HeroicKatora
commented
Jan 17, 2019
|
If it's specifically the hint to operator
The best argument why |
This comment has been minimized.
This comment has been minimized.
i thought that's the whole point of explicit awaits?
yeah, so the this syntax is like calling a function that returns a closure then calling that closure in JS. |
This comment has been minimized.
This comment has been minimized.
|
My reading of "syntax-heavy" was closer to "sigil-heavy", seeing a sequence of
I think the rebuttal here is: how many times have you seen |
This comment has been minimized.
This comment has been minimized.
HeroicKatora
commented
Jan 17, 2019
I don't see how this choice of syntax has an influence on the number of times you have to explicitely name your result type. I don't think it does not influence type inference any different than However, you all made very good points here and the more examples I contrive with it (I edited the original post to always contain both syntax version), the more I lean towards |
This comment has been minimized.
This comment has been minimized.
I expect to see the type equivalent of this (an async fn that returns a Result) all the time, likely even the majority of all async fns, since if what you're awaiting is an IO even, you'll almost certainly be throwing IO errors upwards. Linking to my previous post on the tracking issue and adding a few more thoughts. I think there's very little chance a syntax that does not include the character string
My own ranking amonst these choices changes every time I examine the issue. As of this moment, I think using the obvious precedence with the sugar seems like the best balance of ergonomics, familiarity, and comprehension. But in the past I've favored either of the two other prefix syntaxes. For the sake of discussion, I'll give these four options these names:
(I've specifically used "postfix keyword" to distinguish this option from other postfix syntaxes like "postfix macro".) |
This comment has been minimized.
This comment has been minimized.
HeroicKatora
commented
Jan 17, 2019
•
|
One of the shortcomings of 'blessing' An example where we can recover from an error case:
This issue does not occur for actual post-fix operators:
|
This comment has been minimized.
This comment has been minimized.
These are different expressions, the dot operator is higher precedence than the "obvious precedence"
This has the exact same ambiguity of whether |
This comment has been minimized.
This comment has been minimized.
HeroicKatora
commented
Jan 17, 2019
•
Not exactly the same.
That's part of the confusion, replacing
However, under 'Useful precedence' and
whereas in obvious precedence this transformation no longer applies at all without extra paranthesis, but at least intuition still works for 'Result of Future'. And what about the even more interesting case where you await at two different points in a combinator sequence? With any prefix syntax this, I think, requires parantheses. The rest of Rust-language tries to avoid this at lengths to make 'expressions evaluate from left to right' work, one example of this is auto-ref magic. Example to show that this gets worse for longer chains with multiple await/try/combination points.
What I'd like to see avoided, is creating the Rust analogue of C's type parsing where you jump between Table entry in the style of @withoutboats:
|
This comment has been minimized.
This comment has been minimized.
|
I'm strongly in favor of a postfix await for various reasons but I dislike the variant shown by @withoutboats , primarily it seems for the same reasons. Eg. First lets look at a similar table but adding a couple more postfix variants:
Now let's look at a chained future expression:
And now for a real-world example, from let res: MyResponse = client.get("https://my_api").send().await?.json().await?; |
This comment has been minimized.
This comment has been minimized.
|
Actually i think every separator looks fine for postfix syntax, for example: |
This comment has been minimized.
This comment has been minimized.
mzji
commented
Jan 18, 2019
•
|
Could postfix macro (i.e.
Also postfix macro requires less effort to be implemented, and is easy to understand and use. |
This comment has been minimized.
This comment has been minimized.
Also it's just using a common lang feature (or at least it would look like a normal postfix macro). |
This comment has been minimized.
This comment has been minimized.
luover
commented
Feb 10, 2019
•
|
can we get the syntax like C# or JS ? most developers use it in the world, I don't like rust use new syntax or Inconsistent, Rust is also hard for new people to learn. |
This comment has been minimized.
This comment has been minimized.
I60R
commented
Feb 10, 2019
|
The following would be appendix to my post about using postfix We should use experience of many other languages instead And we actually use it. But that not means we must completely repeat the same experience. Also, when arguing in this way against
We should use Those people may love
|
This comment has been minimized.
This comment has been minimized.
markrendle
commented
Feb 10, 2019
•
In C# you write this: try
{
var response = await client.GetAsync("www.google.com");
var json = await response.ReadAsStreamAsync();
var somethingElse = await DoMoreAsyncStuff(json);
}
catch (Exception ex)
{
// handle exception
}Regarding the propagate-error-chaining argument, e.g. let response = await? getProfile();Another thing that just occurred to me: what if you want to // Prefix
let userId = match await response {
Ok(u) => u.id,
_ => -1
};// Postfix
let userId = match response {
Ok(u) => u.id,
_ => -1
} await;Additionally, would an let userId = match response {
Ok(u) => somethingAsync(u),
_ => -1
} await; // Are we awaiting match or response here?If you have to await both things, you'd be looking at something like // Prefix - yes, double await is weird and ugly but...
let userId = await match await response {
Ok(u) => somethingAsync(u),
_ => -1
} await;// Postfix - ... this is weirder and uglier
let userId = match response {
Ok(u) => somethingAsync(u),
_ => -1
} await await;Although I guess that might be // Postfix - ... this is weirder and uglier
let userId = match response await {
Ok(u) => somethingAsync(u),
_ => -1
} await;(Programming language design is hard.) |
This comment has been minimized.
This comment has been minimized.
markrendle
commented
Feb 10, 2019
|
Regardless, I'm going to reiterate that Rust has precedence for unary operators being prefix with |
This comment has been minimized.
This comment has been minimized.
rolandsteiner
commented
Feb 10, 2019
Beauty is in the eye of the beholder.
Regardless, I feel that the discussion is now circling the drain. Without bringing up any new discussion points, there is no use to re-iterate the same positions again and again. FWIW, I'm happy to get |
This comment has been minimized.
This comment has been minimized.
Pzixel
commented
Feb 10, 2019
|
@markrendle I'm not sure what you answering on
I know how I write in C#. I said "imagine how could it look it we didn't have exceptions". Because Rust doesn't.
It was already discussed twice or trice, please, read the topic. In a nutshell: it's an artificial construct, that won't work well if we will have something additional to
// Real postfix
let userId = match response await {
Ok(u) => u.id,
_ => -1
};// Real Postfix 2 - looks fine, except it's better to be
let userId = match response await {
Ok(u) => somethingAsync(u),
_ => ok(-1)
} await;// Real Postfix 2
let userId = match response await {
Ok(u) => somethingAsync(u) await,
_ => -1
}; |
This comment has been minimized.
This comment has been minimized.
orthoxerox
commented
Feb 10, 2019
|
As another user of C# I'll say that its prefix syntaxes: However, any syntax will be better than chaining futures explicitly, even a pseudo-macro. I will welcome any resolution. |
This comment has been minimized.
This comment has been minimized.
yasammez
commented
Feb 10, 2019
|
@orthoxerox You raise a very good point. In my dayjob I mostly write Java and I despise the new operator to a level that all my classes which need explicit instantiation (a surprisingly rare occurence when you use builder patterns and dependency injection) have a static factory method just that I can hide the operator. |
This comment has been minimized.
This comment has been minimized.
markrendle
commented
Feb 10, 2019
•
I'm guessing this is probably a language barrier thing because that's not at all what you said, but I'll accept it may have been what you meant. Anyway, as @rolandsteiner said, the important thing is that we get some form of async/await, so I'm happy to await the core team's decision, and all y'all postfix fans can the core team's decision await. |
This comment has been minimized.
This comment has been minimized.
markrendle
commented
Feb 10, 2019
|
@yasammez Come to C#. In v8.0 we get to just use new() without the type name :) |
This comment has been minimized.
This comment has been minimized.
phaux
commented
Feb 12, 2019
|
I'm just gonna throw out some ideas for the postfix operator. foo()~; // the pause operator
foo()^^; // the road bumps operator
foo()>>>; // the fast forward operator |
This comment has been minimized.
This comment has been minimized.
|
Not saying if postfix operator is the way to go or not, but personally I find |
This comment has been minimized.
This comment has been minimized.
earthengine
commented
Feb 13, 2019
|
I was proposed I proposed this because it is like an echo talking:
|
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
|
I can't tell if this thread have hit peak ridiculousness or are we onto something here. |
This comment has been minimized.
This comment has been minimized.
wq7
commented
Feb 14, 2019
|
I think |
This comment has been minimized.
This comment has been minimized.
superriva
commented
Feb 14, 2019
•
|
Could be:
|
This comment has been minimized.
This comment has been minimized.
|
We could introduce a trigraph like |
This comment has been minimized.
This comment has been minimized.
|
At first, I was strongly on the side of having a delimiter-required prefix syntax as It makes me think that it is somewhat unfortunate that now there is a confusion in attempting to compare it to other languages with that regard. The closest analog really is the monad I have, therefore, been convinced of the merit of the postfix sigil operator. The downside of this, is that the sigil I would've most preferred is taken by the never type. I would've preferred |
This comment has been minimized.
This comment has been minimized.
I tend to disagree. The behaviour you mention is not a property of |
This comment has been minimized.
This comment has been minimized.
I60R
commented
Feb 15, 2019
•
|
Probably the problem with weirdly looking Then providing a better glyph and some ligatures for popular programming fonts (or at least for Mozilla's In all other cases, for me it don't looks that E.g. the following code uses different than // A
if db.is_trusted_identity(recipient.clone(), message.key.clone())@? {
info!("recipient: {}", recipient);
}
// B
match db.load(message.key)@? {
Some(key) => key,
None => {
return Err(/* [...] */);
}
};
// C
let mut res = client.get(&script_src)
.header("cookie", self.cookies.read().as_header_value())
.header("user-agent", USER_AGENT)
.send()@?
.error_for_status()?;
// D
let mut res: InboxResponse = client.get(inbox_url)
.headers(inbox_headers)
.send()@?
.error_for_status()?
.json()@?;
// E
let mut res: Response = client.post(url)
.multipart(form)
.headers(headers.clone())
.send()@?
.error_for_status()?
.json()@?;
// F
async fn request_user(self, user_id: String) -> Result<User> {
let url = format!("users/{}/profile", user_id);
let user = self.request(url, Method::GET, None, true)@?
.res.json::<UserResponse>()@?
.user
.into();
Ok(user)
}Expand for comparison how it looks with regular ANSI `@`// A
if db.is_trusted_identity(recipient.clone(), message.key.clone())@? {
info!("recipient: {}", recipient);
}
// B
match db.load(message.key)@? {
Some(key) => key,
None => {
return Err(/* [...] */);
}
};
// C
let mut res = client.get(&script_src)
.header("cookie", self.cookies.read().as_header_value())
.header("user-agent", USER_AGENT)
.send()@?
.error_for_status()?;
// D
let mut res: InboxResponse = client.get(inbox_url)
.headers(inbox_headers)
.send()@?
.error_for_status()?
.json()@?;
// E
let mut res: Response = client.post(url)
.multipart(form)
.headers(headers.clone())
.send()@?
.error_for_status()?
.json()@?;
// F
async fn request_user(self, user_id: String) -> Result<User> {
let url = format!("users/{}/profile", user_id);
let user = self.request(url, Method::GET, None, true)@?
.res.json::<UserResponse>()@?
.user
.into();
Ok(user)
} |
This comment has been minimized.
This comment has been minimized.
Pzixel
commented
Feb 15, 2019
Then you probably want unambiguous
It's not true. It's actually is like most other futures from other languages. Difference between "run until first await when spawned" vs "run until first await when polled" is not that big. I know because I worked with both. If you actually think "when this difference comes to play" you will find only corner case. e.g. you will get error when creating future on first poll instead of when it's created. They could be internally different, but they are quite the same from user perspective so it doesn't make sense to me to make a distinction here. |
This comment has been minimized.
This comment has been minimized.
That's not the difference we're talking about, though. We're not talking about lazy vs eager -to-first-await. What we're talking about is that
Personally, I think this mismatch of semantics is large enough to counteract the familiarity of Anecdotally, for me when I first learned At this point, I believe the discussion on the differences in Rust's |
This comment has been minimized.
This comment has been minimized.
Pzixel
commented
Feb 15, 2019
|
@CAD97 yes, I see your position, but I think the discinction is not that big. You got me, I got you. So let the discussion have its flow. |
This comment has been minimized.
This comment has been minimized.
orthoxerox
commented
Feb 15, 2019
If even people intimately familiar with Rust make that mistake, is it really a mistake? |
This comment has been minimized.
This comment has been minimized.
|
So, we had a number of discussions about async-await at the Rust All Hands. In the course of those discussions, a few things became clear: First, there is no consensus (yet) in the lang team about the await syntax. There are clearly a lot of possibilities and strong arguments in favor of all of them. We spent a long time exploring alternatives and produced quite a lot of interesting back-and-forth. An immediate next step for this discussion, I think, is to convert those notes (along with other comments from this thread) into a kind of summary comment that lays out the case for each variant, and then to continue from there. I'm working with @withoutboats and @cramertj on that. Stepping back from the syntax question, another thing that we plan to do is an overall triage of the status of the implementation. There are a number of current limitations (e.g., the implementation requires TLS under the hood presently to thread information about the waker). These may or may not be blockers to stabilization, but regardless they are issues that do need to ultimately be addressed, and that is going to require some concerted effort (in part from the compiler team). Another next step then is to conduct this triage and generate a report. I expect us to conduct this triage next week, and we'll have an update then. In the meantime, I am going to go ahead and lock this thread until we've had a chance to produce the above reports. I feel like the thread has already served its purpose of exploring the possible design space in some depth and further comments aren't going to be particularly helpful as this stage. Once we have the aforementioned reports in hand, we will also lay out the next steps towards reaching a final decision. (To expand a bit on the final paragraph, I am pretty interested in exploring alternative ways to explore the design space beyond long discussion threads. This is a much bigger topic than I can address in this comment, so I won't go into details, but suffice to say for now that I am quite interested in trying to find better ways to resolve this -- and future! -- syntax debates.) |
rust-lang
locked and limited conversation to collaborators
Feb 15, 2019
This comment has been minimized.
This comment has been minimized.
|
Async-await status report: http://smallcultfollowing.com/babysteps/blog/2019/03/01/async-await-status-report/ Relative to my previous comment, this contains the results of the triage and some thoughts about syntax (but not yet a full syntax write-up). |
nikomatsakis
added
the
AsyncAwait-Blocking
label
Mar 5, 2019
This comment has been minimized.
This comment has been minimized.
|
Marking this issue as blocking for async-await stabilization, at least for the time being. |
cramertj commentedJan 15, 2019
•
edited by scottmcm
Before commenting in this thread, please check #50547 and try to check that you're not duplicating arguments that have already been made there.
Notes from shepherds:
If you're new to this thread, consider starting from #57640 (comment), which was followed by three great summary comments, the latest of which were #57640 (comment). (Thanks, @traviscross!)