Live data from Hacker News

A final proposal for Rust await syntax

boats.gitlab.io

211–220 of 265 posts

Re: A final proposal for Rust await syntax

#211
post #206
post #205

Earlier quoted context omitted.

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) } t…

Switches between keeping the args for the function's full duration, or returning a closure (async or not) that doesn't hold onto them.

Here's the problem in terms of normal OS threads:

    fn f(r: &'a i32) -> i32 { ... *r ... }

    // oh no, I can't do this:
    let i = 42;
    thread::spawn(|| f(&i));
Here's the workaround:

    fn f(r: &'a i32) -> impl FnOnce() -> i32 {
        let i = *r;
        || ... i ...
    }

    // now I can do this:
    let i = 42;
    thread::spawn(f(&i));
In this case, and the analogous lightweight threads case you're describing, and the "implicit await" post I originally linked, the workaround forces the caller to change its syntax. From `|| f(&i)` to `f(&i)`, or from `async { f(&i) }` to `f(&i)`, or from `future { f(&i) }` to `f(&i)`.

But in async/await as currently proposed and implemented, the transformation goes from this...

    async fn f(r: &'a i32) -> i32 { ... *r ... }

    // oh no, I can't do this:
    let i = 42;
    task::spawn(f(&i));
...to this:

    fn f(r: &'a i32) -> impl Future {
        let i = *r;
        async { ... i ... }
    }

    // now I can do this:
    let i = 42;
    thread::spawn(f(&i));
You can imagine someone originally writing the first version, when all their callers just immediately `await` so it's okay if the reference sticks around. But then another caller wants to write something like the above, so they make the transformation above.

Under today's futures, all the other call sites keep working (`f(&i).await`) and the new use case starts working. Under our proposals, that transformation would break everyone just using the `f(&i)` syntax, so it probably wouldn't happen, and instead the new caller would have to write this:

    thread::spawn(async move {
        // move `i` in here, or worse, stuff it in an Arc, even though it's only needed for setup!
        let my_i = i;
        f(&my_i)
    });

Re: A final proposal for Rust await syntax

#212
post #206
post #205

Earlier quoted context omitted.

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) } t…

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

Making another reply because this is completely unrelated...

Rust already tried that. The problem is that Rust has a hard requirement as a systems language to support, at least, native I/O APIs, and the green threads implementation added a pervasive cost to that support because all standard library I/O had to go through the same machinery just in case it was happening in a green thread.

That overhead made green threads themselves basically no faster than native threads, so they were dropped before 1.0 to make room for a new solution to come along eventually. Futures and async is that solution, and it turns out to be much lighter weight than green threads ever could have been anyway- no allocating stacks, no switching stacks, no interferering with normal I/O.

The syntax could have been different, but the implementation is far better this way.

Re: A final proposal for Rust await syntax

#213
post #55

Earlier quoted context omitted.

> Rust is already a weird language to come to from the likes of python, Java, or Javascript Every time I see a statement like this, I remember a (paraphrased) statement from Rich Hickey: "[musical] instruments are made for people who can play them!". I think unless you are specifically designing a beginner language (like Scratch), you should not take into consideration "ease of use" or "familiarity" arguments.

Counterpoint: APL and perl vs. python. Python did take usability into account and familiarity. UX is important. Developers are users. As a language (or in general tool) designer you have a responsibility to make that tool easy to use, and difficult to misuse. Familiarity is a big part of that, although ease of use is bigger (which is probably why python got the traction it did despite being unfamiliar to people who c…

Counter to your counterpoint: APL did take usability into account. Dr Iverson got annoyed with how inconsistent and hard to read normal math notation was, and how many problems that caused in its usability, and invented Iverson notation to fix that - a tool to be usable by people writing on blackboards to show other people mathematical ideas.

Years later, it was used at IBM to describe what the IBM 360 computer would do. After that, it got turned into APL\360 around 1962 (i.e there weren't all that many programming languages to be familiar with, then). The book "APL\360 An Interactive Guide" by L. Gilman, 1970, has a foreword which says:

APL is clearly gaining acceptance at this time as a computer programming language. This acceptance is not hard to understand. APL is one of the most concise, consistent, and powerful programming languages ever devised. (UX is important!)

and

From a pedagogical standpoint APL has a number of advantages. The material can be taught and used in small pieces. A student can be trying his hand on simple operations after five minutes of instruction. What he doesn't know won't hurt him (a statement that cannot be made about most other languages). If he tries something illegal such as division by zero or adding a number and a letter, he gets an understandable error message and is free to try something else. Nothing the user can do will cause the system to crash. (Usability!)

and

It is indubitably true that a "clever" programmer can use these advanced operators in such a way as to produce an "opaque" program, that is, one so compact and concise as to be nearly impossible for anyone else to understand. Whatever else may be said about such programs, which are questionable in many contexts anyway, they should not be used in demonstrations of APL. Experienced programmers who have seen APL demonstrated in terms of the fantastic cleverness angle sometimes criticize the language as being hard to understand, when their criticism more properly should have been directed at the demonstrator. Such misplaced cleverness is not to be found in this book. All operators are thoroughly covered, but there is no attempt to show off the ingenuity of the authors in writing ingeniously condensed programs.

Re: A final proposal for Rust await syntax

#214
post #109
post #55

Earlier quoted context omitted.

> Rust is already a weird language to come to from the likes of python, Java, or Javascript Every time I see a statement like this, I remember a (paraphrased) statement from Rich Hickey: "[musical] instruments are made for people who can play them!". I think unless you are specifically designing a beginner language (like Scratch), you should not take into consideration "ease of use" or "familiarity" arguments.

I wouldn't use a programming language whose designer had this attitude.

Erik Naggum, Comp.Lang.Lisp, 1997:

"what makes _me_ sad is the focus on "most folks" and "Joe Sixpack".

why are we even _thinking_ about home computer equipment when we wish to attract professional programmers?

in _every_ field I know, the difference between the professional and the mass market is so large that Joe Blow wouldn't believe the two could coexist. more often than not, you can't even get the professional quality unless you sign a major agreement with the vendor -- such is the investment on both sides of the table. the commitment for over-the-counter sales to some anonymous customer is _negligible_. consumers are protected by laws because of this, while professionals are protected by signed agreements they are expected to understand. the software industry should surely be no different. (except, of course, that software consumers are denied every consumer right they have had recognized in any other field.)

Microsoft and its ilk has done a marvelous job at marketing their software in the mass market so that non-professional programmers pick them up and non-programmers who decide where the money should be wasted will get a warm fuzzy feeling from certain brand names. I mean, they _must_ recognize that nothing else they buy for their company is advertised in the newspapers that morning and they aren't swayed by consumer ads when they buy office or plant equipment, are they? so _why_ do they swallow this nonsense from the mass-marketing guys hook, line, and sinker?

they don't make poles long enough for me want to touch Microsoft products, and I don't want any mass-marketed game-playing device or Windows appliance _near_ my desk or on my network. this is my _workbench_, dammit, it's not a pretty box to impress people with graphics and sounds. when I work at this system up to 12 hours a day, I'm profoundly uninterested in what user interface a novice user would prefer.

I'm reminded of the response to how people of little or no imagination were complaining about science fiction and incredibly expensive space programs: "the meek can _have_ the earth -- we have other plans".

no, this is not elitist, like some would like to believe in order to avoid thinking about the issues. this is just calling attention to the line between amateurs and professionals, between consumers and producers, that is already there in _every_ field. I want people to wake up to this difference and _reject_ the consumer ads when they look for professional tools. if it's marketed to tens of millions of people, it is _not_ for the professional programmer, and not for you. if its main selling point is novice-friendliness, ignore it unless you _are_ a novice. (and if you are a novice trying to sell your services in a professional market, get the hell out of the way.)

https://groups.google.com/forum/#!topic/comp.lang.lisp/HCMKe...

Re: A final proposal for Rust await syntax

#215

I wish more language constructs used postfix notation. It looks so natural to me when reading from left to right. Consider: [a+2 for a in as if a > 3] Vs as.filter(a -> a > 3).map(a -> a + 2) Sometimes I even wonder why variable assignment has to precede the assigned expression.

Hardly natural because the site where the binding is introduced follows the expression that uses the binding. When you type from left to right, editors will highlight that unknown binding until after you bind it.

Re: A final proposal for Rust await syntax

#216
post #211
post #206

Earlier quoted context omitted.

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) } t…

Switches between keeping the args for the function's full duration, or returning a closure (async or not) that doesn't hold onto them. Here's the problem in terms of normal OS threads: fn f (r: &'a i32) -> i32 { ... *r ... } // oh no, I can't do this: let i = 42; thread::spawn(|| f(&i)); Here's the workaround: fn f (r: &'a i32) -> impl FnOnce() -> i32 { let i = *r; || ... i ... } // now I can do this: let i = 42; thr…

I see. Would that be such a disaster under your proposal? Original code is:

   async fn f(r: &'a i32) -> i32 { ... *r ... }
Having some callers future{ f(&i) }.await().

Now the new caller comes in, so we add a function f_future:

   fn f_future(r: &'a i32) -> impl Future         
        let i = *r;
        async { ... i ... }
   }
The new caller uses f_future and the old callers keep using f. To prevent duplication we can factor out ... i ... into a function g(i) and do g(*r) in the async fn f. The other callers can migrate from future{ f(&i) }.await() to f_future(&i).await() over time.

It's not as ideal as not having to change the signature at all, but signature changes can be dealt with. Or is this a big problem with OS threads?

Re: A final proposal for Rust await syntax

#217
post #212
post #206

Earlier quoted context omitted.

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) } t…

> This proposal does raise another question: why not just green threads, and remove the concept of async functions entirely? Making another reply because this is completely unrelated... Rust already tried that. The problem is that Rust has a hard requirement as a systems language to support , at least, native I/O APIs, and the green threads implementation added a pervasive cost to that support because all standard li…

Couldn't green threads in principle be implemented the same way as your async proposal? The compiler could infer which functions need to be marked async. To support separate compilation it might need to compile two versions of each function, an async one and a normal one. You'd have exactly what you have in your proposal, except you never have to write async fn. You could still have blocking & non-blocking IO. It wouldn't totally unify green threads with OS threads, but Futures/async/await don't do that either.

Re: A final proposal for Rust await syntax

#218

Earlier quoted context omitted.

Counterpoint: APL and perl vs. python. Python did take usability into account and familiarity. UX is important. Developers are users. As a language (or in general tool) designer you have a responsibility to make that tool easy to use, and difficult to misuse. Familiarity is a big part of that, although ease of use is bigger (which is probably why python got the traction it did despite being unfamiliar to people who c…

Counter to your counterpoint: APL did take usability into account. Dr Iverson got annoyed with how inconsistent and hard to read normal math notation was, and how many problems that caused in its usability, and invented Iverson notation to fix that - a tool to be usable by people writing on blackboards to show other people mathematical ideas. Years later, it was used at IBM to describe what the IBM 360 computer would…

> APL did take usability into account.

Perhaps usability for a certain, very specific, subset of people (namely those who are writing code on a whiteboard?) But math notaion is not programming, they have different needs.

> UX is important!

Note that concise and powerful also apply to perl. Concise, in programming does not equal good UX. Often they're antithetical. (This also doesn't mean that verbose is "good UX" either. Programming language design, like programming, is about finding the right abstractions and providing them.)

Compare to ABC (a language python was heavily inspired by) using `:` to declare a function (ie. `def my_f():`) despite it being unnecessary, the language was parsable without it. But they did user studies, and those found that the colon helper readers understand the blocks better.

> Usability

Granted this may have been an improvement in usability. APL, being a higher level language, avoids many of the problems that C and co had, and I've never used basic/fortran any of the other early languages that were popular at the same time.

That said, I have to strongly disagree with the last paragraph. A good tool discourages misuse. Inscrutable programs are misuse, "Readability counts". A language that encourages, or doesn't discourage, inscrutable programs isn't as good a language as one that does (at least if you consider inscrutable programs to be misuse, and it appears that you, I, and the APL author all agree on that).

Re: A final proposal for Rust await syntax

#219
post #216
post #211

Earlier quoted context omitted.

Switches between keeping the args for the function's full duration, or returning a closure (async or not) that doesn't hold onto them. Here's the problem in terms of normal OS threads: fn f (r: &'a i32) -> i32 { ... *r ... } // oh no, I can't do this: let i = 42; thread::spawn(|| f(&i)); Here's the workaround: fn f (r: &'a i32) -> impl FnOnce() -> i32 { let i = *r; || ... i ... } // now I can do this: let i = 42; thr…

I see. Would that be such a disaster under your proposal? Original code is: async fn f (r: &'a i32) -> i32 { ... *r ... } Having some callers future{ f(&i) }.await(). Now the new caller comes in, so we add a function f_future: fn f_future (r: &'a i32) -> impl Future let i = *r; async { ... i ... } } The new caller uses f_future and the old callers keep using f. To prevent duplication we can factor out ... i ... into…

I agree, there's plenty of ways to work around it, and I'd prefer any of them to the syntactic mess we're in now. I'm not the one making the decisions, though. :)

Re: A final proposal for Rust await syntax

#220
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…

If you’re able to figure out how to write working rust code, you can learn how to google where ‘await’ goes.
Post reply on HN