Live data from Hacker News

Update on await syntax in Rust

boats.gitlab.io

161–170 of 199 posts

Re: Update on await syntax in Rust

#161
post #152

Earlier quoted context omitted.

`fut.await()` looks too magical. There's no hint in that expression that it's anything other than a method call. You could argue the same about `fut.await`, except in this case `await` is a keyword and thus the expression `x.await` is known to be an await expression without knowing anything about `x`, whereas `fut.await()` requires knowing the type of `fut` to understand that the method call `.await()` resolves to th…

If we're gonna have await share syntax with some other language feature, method syntax is IMO better than field syntax- it semantically blocks the function until the callee is complete, it evaluates to a temporary instead of an lvalue, it can unwind the caller (via cancelation, like a panic). You could implement that syntax and still make `await` a keyword, without doing the usual method lookup and without letting no…

This just then boils down to saying "the await keyword should have required parens after it", which is a different argument than saying "it should be a lang item".

Re: Update on await syntax in Rust

#162
post #158

Earlier quoted context omitted.

> Futures / async-await is an idiom that makes async code easier to reason about, not something that provides any new theoretical foundation or any functionality that wasn't possible before. So, this is true in GC'd languages, but if you see my link below, in Rust, async/await does let you write code that was previously impossible. This is because the compiler can't understand lifetimes in the way that the code is wr…

I meant my comment a bit more broadly & abstractly than that. You can write code in a new way with await, and it does help you with how to factor & manage the code. But it does not fundamentally let you make parallel requests that you couldn't make before, somehow . The code you needed before await might be ugly and stateful and not idiomatic, but it was still possible to make requests and wait for them, before await…

Okay, yeah, that is fair. I have written enough raw futures in anger to be like "it's not practically possible", but you're also right that it is actually practically possible, regardless.

Re: Update on await syntax in Rust

#163

Earlier quoted context omitted.

`fut.await()` looks too magical. There's no hint in that expression that it's anything other than a method call. You could argue the same about `fut.await`, except in this case `await` is a keyword and thus the expression `x.await` is known to be an await expression without knowing anything about `x`, whereas `fut.await()` requires knowing the type of `fut` to understand that the method call `.await()` resolves to th…

> There's no hint in that expression that it's anything other than a method call. Compared with `fut.await`... > There's no hint in that expression that it's anything other than a field access. At least there is precedence for functions to have magical compiler-ness for 'doing stuff', instead of overloading field access which is one of the simplest operations you can have. > ... requires knowing the type of `fut` ...…

> At least there is precedence for functions to have magical compiler-ness for 'doing stuff', instead of overloading field access which is one of the simplest operations you can have.

Except we don't actually have precedence for "I'm going to declare a function, but it's not actually implemented in Rust, instead it's a cue to the compiler to do other magical stuff". Lang items are in fact the opposite; they're telling the compiler "here's the implementation for an extension point you've defined". For example the "panic_impl" lang item is a function that's invoked when code panics. The closest I can think of to what you're referring to is the fact that the type identified as "owned_box" has special dereferencing behavior, but that can be modeled as the compiler providing an implementation for an un-namable trait on the owned_box item where the trait represents the new deref behavior (just as how Deref and DerefMut represent the existing deref behaviors).

> tbh, you don't need to know how await works.

You need to know the type in order to determine if `fut.await()` is invoking this magical semantics-altering compiler behavior or if it's just invoking a method that happens to be named `await` (given that the lang_item proposal means await is no longer a keyword).

> Contrast with the field access one and you don't even know its something else entirely.

Except you do, because await is a keyword. It's impossible in Rust 2018 to have a field named await, and so any expression that looks like `x.await` is guaranteed to be an invocation of the await behavior.

> I guess I just don't buy that it can't be a method.

It cannot be a method that is implemented as a call to an intrinsic. The await keyword requires rewriting the function that invokes it.

Re: Update on await syntax in Rust

#164
post #158

Earlier quoted context omitted.

I meant my comment a bit more broadly & abstractly than that. You can write code in a new way with await, and it does help you with how to factor & manage the code. But it does not fundamentally let you make parallel requests that you couldn't make before, somehow . The code you needed before await might be ugly and stateful and not idiomatic, but it was still possible to make requests and wait for them, before await…

Okay, yeah, that is fair. I have written enough raw futures in anger to be like "it's not practically possible", but you're also right that it is actually practically possible, regardless.

Oh, practical is also separate from my answer, since my reading of the question was strictly "theoretical". ;)

Maybe about a decade ago I wrote a raw futures system for async asset loading off DVDs on the Nintendo Wii in C++, and it had to play nice with the real-time rendering system. I didn't stand back far enough to abstract it the way futures work today, and as a result it was an absolutely awful experience. It was so difficult to understand and debug, I dug myself an enormous hole. Having used futures in JavaScript and Scala and other languages since, I'm certain having real futures would have saved me months of crunching.

I would agree that await syntax brings a huge degree of practicality to what was previously theoretically possible.

Re: Update on await syntax in Rust

#165
I am amazed how well documented and transparent was the whole process. They new the decision would be difficult to make and there will always be people who wouldn't like the final syntax so they made sure to state their reasoning and trade offs clearly. Fantastic job. Can't wait for August. Thanks!

Re: Update on await syntax in Rust

#166

Earlier quoted context omitted.

It cannot exist as a macro or function, because it transforms the code in ways they can’t. Macros can transform code, but the final output isn’t something representable in stable Rust, and so it would not compile post-expansion.

It seems there's a conflict between the logical flow of prefixes going right to left and postfixes going left to right. I wonder if there's any value (for other future languages) in having syntax to swap post and prefix for convenience instead of (await doSomething()).somethingElse() doSomething()@await.somethingElse()

That seems to be what this proposal is already working toward, eg with the discussion of also supporting .match.

keyword EXPR MAYBEBODY EXPR.keyword MAYBEBODY

Re: Update on await syntax in Rust

#167

Earlier quoted context omitted.

It seems there's a conflict between the logical flow of prefixes going right to left and postfixes going left to right. I wonder if there's any value (for other future languages) in having syntax to swap post and prefix for convenience instead of (await doSomething()).somethingElse() doSomething()@await.somethingElse()

That seems to be what this proposal is already working toward, eg with the discussion of also supporting .match. keyword EXPR MAYBEBODY EXPR.keyword MAYBEBODY

From the post:

> “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.

Re: Update on await syntax in Rust

#168

Earlier quoted context omitted.

@steveklabnik My apologies if I appeared to make light of the Rust community’s contributions. My definition of religious war is butting heads about problems that don’t have a clear right answer (tabs/spaces). If there are new things considered by the community that make the discussion worthwhile then it’s absolutely worth having; but if it causes people to burn out on the community down the line then maybe more moder…

> My apologies if I appeared to make light of the Rust community’s contributions. Not at all! > My definition of religious war is butting heads about problems that don’t have a clear right answer (tabs/spaces). So this is where we get into the meat of it. I personally do believe that a lot of the discussion was around things that don't have a clear right answer. But (and the post mentions this a bit), that doesn't me…

Maybe a better way I could phrase my concern would be whether the benefit to having the debate outweighs the cost, and what metrics Rust might keep track of that inform what the cost may be at every stage of the discussion.

The debate will always make sense for some time, but after a while it may block discussion of other features the Rust team might like to have for a roadmap milestone. Maybe having two forks with a tiny subset of async/await implemented (with something like tracer bullet development) would give stakeholders useful information on what the implementation cost would look like, what testing coverage overhead would be, and other metrics that balance the theoretical with the practical.

As to the statement that async/await was a smaller part of grokking concurrency in a language, maybe a better blog post might be this one:

https://vorpus.org/blog/notes-on-structured-concurrency-or-g...

Some helpful background: Python has the notion of a global interpreter lock (GIL), which prevents concurrent CPU-bound tasks in threads from being effectively parallelized in Python. As a result, most development of concurrent tasks are I/O-bound, or call some C/Fortran library where the GIL does not block CPU-bound tasks, or use a process pool. I don't think this should affect the discussion because I think the issues with concurrency he talks about don't care about what resource it's bound by, they may just be more evident with I/O-bound tasks or provide greater perspective.

So `njs` (a Python core developer) went and developed an async I/O library called `trio`, which has the concept of "nurseries", which is a container for dynamic tasks. Nursery exit will block on the dynamic tasks within exiting, in order to make sure that the concurrent tasks within can appear as a single black box to the outside world. I believe he was saying how the async/await feature in Python should be combined with other control flow primitives, because otherwise creating tasks that outlive the parent, error propagation, and resource cleanup may result in spaghetti behavior and spaghetti code. He also claims that clean control flow also results in new features that assume said control flow, which makes development more powerful. This goes back to my assertion that async/await isn't the end-all/be-all in terms of making concurrent code grokkable.

One quote from that blog post that mentions Rust and thread correctness (I'm not sure if it's outdated):

""" Go statements break error handling. Like we discussed above, modern languages provide powerful tools like exceptions to help us make sure that errors are detected and propagated to the right place. But these tools depend on having a reliable concept of "the current code's caller". As soon as you spawn a task or register a callback, that concept is broken. As a result, every mainstream concurrency framework I know of simply gives up. If an error occurs in a background task, and you don't handle it manually, then the runtime just... drops it on the floor and crosses its fingers that it wasn't too important. If you're lucky it might print something on the console. (The only other software I've used that thinks "print something and keep going" is a good error handling strategy is grotty old Fortran libraries, but here we are.) Even Rust – the language voted Most Obsessed With Threading Correctness by its high school class – is guilty of this. If a background thread panics, Rust discards the error and hopes for the best. """

Re: Update on await syntax in Rust

#169

Earlier quoted context omitted.

Please don't do that. I had to look several times to rewrite it as var temp1 = await StartRequest(); var temp2 = await temp1.GetBody(); var value = temp2.Root; which is instantly grokked.

This is a great example of why syntax and semantics can't be separated. This works great in GC'd languages, but in Rust, these two things may not be equivalent. With the distinction between owned and temporary values, this may change the lifetime of what stuff is in scope and when. This is reduced a bit with non-lexical lifetimes, but it's not, strictly speaking, actually equivalent.

I totally get where you coming from, and even in non GC'd languages such as C++ lifetimes are extended (or not...) when you break down expression.

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.

Re: Update on await syntax in Rust

#170
post #152

Earlier quoted context omitted.

If we're gonna have await share syntax with some other language feature, method syntax is IMO better than field syntax- it semantically blocks the function until the callee is complete, it evaluates to a temporary instead of an lvalue, it can unwind the caller (via cancelation, like a panic). You could implement that syntax and still make `await` a keyword, without doing the usual method lookup and without letting no…

This just then boils down to saying "the await keyword should have required parens after it", which is a different argument than saying "it should be a lang item".

hell, I'd support that. A function/method 'does a thing' while field access 'reads a thing'. Rust doesn't have implicit getters and setters like Python might have. It doesn't LOOK like `fut.await` should/could DO anything, but it can.
Post reply on HN