Live data from Hacker News

A final proposal for Rust await syntax

boats.gitlab.io

201–210 of 265 posts

Re: A final proposal for Rust await syntax

#201
post #168

Since `await` is a keyword reserved for this purpose, could `(await expression)` ever mean anything else? What is the cost of ripping off the bandaid and starting there, as at least an alternative syntax that is consistent with the language? match expression await expression It feels like a future proposal, to allow these keywords to be chained more conveniently with a postfix. It's cool that this can possibly be gen…

For what is worth <- is already taken by the placement in syntax, which has been pulled back but rustc needs to continue parsing it to avoid breaking existing code which used it behind cfg flags. It doesn't do anything but it has to be understood. It would be possible to resolve that problem, but it's another thing to be aware of.

Re: A final proposal for Rust await syntax

#202

Earlier quoted context omitted.

You can chain .await, though. let n = future_of_future_of_int .await .await;

True, but you can't chain returns, making this suggested use of a special postfix operation a bad idea imo.

You could chain returns in that case, in principle, but it would just be dead code. The type of a return expression is `!`, the never type.

Re: A final proposal for Rust await syntax

#203
post #105

Earlier quoted context omitted.

Do notation does not solve that problem. It solves the problem of chaining `.and_then` calls, but this is one layer up- do notation would still require each of those awaits to be a separate `a <- b` "statement," which couldn't even be chained in the first place!

Yes, exactly, bindings shouldn't be chained. The whole point of do-notation i.e. syntax that looks like normal bindings is to make them look imperative so that our monkey brains can grok concurrent code easily. Making 'await' expression-oriented is missing the whole point of 'await'. You could have just kept using 'flat_map' or whatever macro.

Well, the real benefit of `await` in an imperative language is that it works inside/across all the usual imperative control flow- loops with break/continue, early return, etc. Whether it's an expression or a binding is mostly orthogonal to that.

Re: A final proposal for Rust await syntax

#204

Earlier quoted context omitted.

Reverse polish notation is an entirely different usage: not the same meaning as used in this proposal. It's also commonly used as the name of a piece of email software, but that's also an unrelated usage to this.

No. RPN and postfix notation refer to the same core idea. From Wikipedia: "Reverse Polish notation (RPN), also known as Polish postfix notation or simply postfix notation, is a mathematical notation in which operators follow their operands, in contrast to Polish notation (PN), in which operators precede their operands." Can you please explain why you wrote "Reverse polish notation is an entirely different usage"? Wit…

Reverse polish notation is a term used in mathematical notation for an a alternative operator syntax to the more common "infix" notation.

The article describes affixing the word "await" to methods in a programming language as a "postfix" and explicitly contrasts it with a "prefix", which is a linguistic term for affixing words, commonly contrasted with the term "suffix".

Re: A final proposal for Rust await syntax

#205
post #193
post #100

Earlier quoted context omitted.

This would have been my favorite approach, and there was a discussion about doing it in Rust: https://internals.rust-lang.org/t/explicit-future-constructi... There were two main objections: First, a Rust async function like this (using explicit lifetime annotations for exposition purposes only, normally they would be elided)... async fn f (r: &'a i32) -> i32 { ... *r ... } ...desugars to a sync function whose return…

I wonder about an altenate timeline where Rust kept its lightweight threads. Marking async calls explicitly instead of marking synchronous calls with await is a step in that direction, because that's also the syntax you have with lightweight threads. What would problem 1 look like in that alternate timeline? In that case the concept of async functions disappears and your first function becomes a normal function. The…

I tried to lean pretty hard into "this syntax is just like threads" in that internals.r-l.org post, when I wrote it, proposing almost exactly what you describe here. Unfortunately problem #1 is not a result of confusion or unnecessary conflation, but a fundamental question of lifetimes- the exact same problem already exists with normal OS threads just as it would with lightweight threads.

That is, a function is always allowed to hold onto its arguments until it returns. If its execution is deferred (e.g. `|| the_function(and, its, arguments)`) for whatever reason (e.g. spawning a lightweight thread or async task), the borrow checker has to consider that those arguments may stick around indefinitely.

Of course, it is 100% doable to force people to work around this just by giving future-building functions a different type. But as I described, this means callers have to add or remove an extra `.run()`/`.await()`/etc. if the API ever switches between the two. This is accepted in the world of threads, but not in the world of futures, because we already have a solution which is "just switch to a future-building function, everyone's already awaiting it."

(Personally, while I certainly see it as a real problem, I would rather we just live with it. It's not hard to work around, and we already do it in the world of threads when necessary, which is rarely.)

Re: A final proposal for Rust await syntax

#206
post #205
post #193

Earlier quoted context omitted.

I wonder about an altenate timeline where Rust kept its lightweight threads. Marking async calls explicitly instead of marking synchronous calls with await is a step in that direction, because that's also the syntax you have with lightweight threads. What would problem 1 look like in that alternate timeline? In that case the concept of async functions disappears and your first function becomes a normal function. The…

I tried to lean pretty hard into "this syntax is just like threads" in that internals.r-l.org post, when I wrote it, proposing almost exactly what you describe here. Unfortunately problem #1 is not a result of confusion or unnecessary conflation, but a fundamental question of lifetimes- the exact same problem already exists with normal OS threads just as it would with lightweight threads. That is, a function is alway…

I still don't understand why #1 is a problem.

> But as I described, this means callers have to add or remove an extra `.run()`/`.await()`/etc. if the API ever switches between the two.

Switches between what though? When you want to do something asynchronously, you indeed build a future and later .await() it. Suppose you then want to build that future in a different way, for example by transforming future { foo(x) } to making foo(x) itself return the future (i.e. moving the future{} block inside foo), possibly because you want to dereference x before building it. Well, the .await() was already there, and doesn't need to be changed. The future{} ... await() pair gets introduced when you want to making things asynchronous, which is exactly as it should be?

Furthermore, isn't that the same with the main async/await proposal? It is indeed true that when you make things async you only have to mark a function as async, and then all the calls to it automatically become async. However, at the end of the day you still need to await those futures or else they won't do anything. So when you switch from sync to async you still need to add those awaits.

The difference seems to me the other way around: with the main proposal you need more awaits (namely, at all points where you want to stay synchronous). With your proposal you need more async/future blocks (namely, at all points where you want to switch to asynchronous).

I think that using the same keyword for async fn and async{} block is a source of confusion, because it makes it seem like async fn is basically like wrapping the body in an async{} block. It's what makes people think that an async fn is like an automatically awaited future, which is a confusing way to think about it and makes it hard to see why this proposal is a good idea (even if it's actually implemented like that under the hood). I think it becomes a lot clearer if you use a different word for these two concepts (like async fn and future{} block), and remove the ::new() and only use future{} syntax.

This proposal does raise another question: why not just green threads, and remove the concept of async functions entirely?

Re: A final proposal for Rust await syntax

#207
post #110

Earlier quoted context omitted.

At first I was firmly in the prefix camp, until I read one of the (massive!) GitHub issues on the subject and came around to postfix. I'd _much_ rather use something like `?` than `.await`, though. Using a keyword just feels wrong in a few different ways.

Can you share said issue?

I don't know about GitHub, but https://paper.dropbox.com/doc/Await-Syntax-Write-Up--AcIbhZ1... summarizes the different tradeoffs pretty well.

Re: A final proposal for Rust await syntax

#208
post #148
post #107

Earlier quoted context omitted.

I've used perl5 for a long time (although these days I've replaced it with python in my toolchain, although I have my fair share of complaints about python as well) and I think your comparison is unfair. Rust's sometimes complicated syntax is made necessary by the very concept of the language. You need to be able to express lifetimes, you need to be able to express generics, you need to be able to strongly type lambd…

> Rust is noisy because it has to represent concepts that simply don't exist in other programming languages. In a few cases having to do with lifetime management and the borrow checker, that's certainly true. But the example at hand is fundamentally just cloning ideas from Javascript.

Actually JS just cloned those ideas from C# and Python.

Re: A final proposal for Rust await syntax

#209
post #32

It seems they've settled on using a postfix approach. I can't help but feel this is a mistake. Rust is already a weird language to come to from the likes of python, Java, or Javascript, and it feels to me like this relatively unknown approach of putting the operator at the end is a mistake that will add another confusing aspect of the language. I feel like the committee has worried about the wrong things when conside…

Literally everyone else uses prefix await, so this will be a problem. But, - this is a lot easier to chain, which is very useful, and - the "future expansion" might bring a prefix await anyway (although introducing two syntaxes for the same thing might be even worse). I'd prefer "f await" to "f.await" because it feels a lot less magical and lets me stick to my intuition that "." is just for stuff implemented by the l…

> Literally everyone else uses prefix await, so this will be a problem.

Kotlin uses `.await()`.

Re: A final proposal for Rust await syntax

#210

Earlier quoted context omitted.

No. RPN and postfix notation refer to the same core idea. From Wikipedia: "Reverse Polish notation (RPN), also known as Polish postfix notation or simply postfix notation, is a mathematical notation in which operators follow their operands, in contrast to Polish notation (PN), in which operators precede their operands." Can you please explain why you wrote "Reverse polish notation is an entirely different usage"? Wit…

Reverse polish notation is a term used in mathematical notation for an a alternative operator syntax to the more common "infix" notation. The article describes affixing the word "await" to methods in a programming language as a "postfix" and explicitly contrasts it with a "prefix", which is a linguistic term for affixing words, commonly contrasted with the term "suffix".

I don't see any reference nor any clear argument supporting your claim that RPN and postfix notation are different.

Again, RPN and postfix notation mean the same thing.

Here are some more references that show how these terms are commonly used:

1. https://en.wikipedia.org/wiki/Infix_notation

2. https://en.wikipedia.org/wiki/Polish_notation

3. https://en.wikipedia.org/wiki/Postfix_notation (which redirects to a page on RPN, which is very strong evidence that your claim is incorrect)

Ok, onto the next thing. You wrote:

> The article describes affixing the word "await" to methods in a programming language as a "postfix" and explicitly contrasts it with a "prefix", which is a linguistic term for affixing words, commonly contrasted with the term "suffix".

I'm not getting your point. I don't think you are summarizing the language accurately. Perhaps you could quote the section of the article at length.

Here is one quote from the article: "The lang team proposes to add the await operator to Rust using this syntax: `expression.await` This is what’s called the “dot await” syntax: a postfix operator formed by the combination of a period and the await keyword. We will not include any other syntax for the await operator."

Here is another quote: "Our previous summary of the discussion focused most of our attention on the prefix vs postfix question. In resolving this question, there was a strong majority in the language team that preferred postfix syntax. To be concrete: I am the only member of the language team that prefers a prefix syntax. The primary argument in favor of postfix was its better composability with methods and the ? operator."

These usages of "prefix" and "postfix" are consistent and idiomatic.

I hope this clears it up.

Post reply on HN