Live data from Hacker News

Update on await syntax in Rust

boats.gitlab.io

121–130 of 199 posts

Re: Update on await syntax in Rust

#121
post #119

Earlier quoted context omitted.

Would the macro case be addressed by also supporting a prefix keyword? The post that states macros can't work does not cover this. From what I can tell: `foo.await!()` would just expand to `(await foo())`, and anyone could write their own `my_await!` macro that works similarly.

I as well think there could be promise in someday exploring that space, but for the moment my enthusiasm for hypothetical postfix macros (which have yet to ever be formally proposed) is somewhat dampened by the realization that `foo.bar.qux.qaz.await!()` would need to expand to `await { foo.bar.qux.qaz }`, which makes me consider how uncomfortable such macros would be to parse (the saving grace of "normal" macro call…

> `foo.bar.qux.qaz.await!()` would need to expand to `await { foo.bar.qux.qaz }`

Interesting point, thank you. I hadn't seen this previously mentioned, and it's definitely a reasonable argument.

> there's no denying that `foo.await?.bar` appears nicer than `foo.await!()?.bar`, especially if there is no guarantee that postfix macros will ever become a thing.

Agreed, for sure. I'm honestly just very concerned about these features because I see them as stepping stones to others. The path from postfix macros seems much brigher than the path from postfix keywords.

I think I agree with you about an await block being a good idea.

Thanks for the response, I think this is the first meaningful response to the prefix await + postfix macro that I've read.

Re: Update on await syntax in Rust

#122

Earlier quoted context omitted.

I think this is a good example to show people who are on the fence about postfix then. I haven't felt strongly about postfix, but this is an eye opener for me.

Yeah, I think the trick is that the theory and practice might be the same. And usually, it's the other way around; spreading it out makes it work, whereas it may not otherwise. For example: fn main() { let world = gives_string().split(" ").next(); println!("{:?}", world); } fn gives_string() -> String { String::from("hello world") } This will fail because the String is temporary, and we're trying to get a reference t…

This has surprised me on occasion. Shouldn't the compiler be smart enough to figure out how long to keep it around for, instead of failing to compile?

Re: Update on await syntax in Rust

#123

Earlier quoted context omitted.

Yeah, I think the trick is that the theory and practice might be the same. And usually, it's the other way around; spreading it out makes it work, whereas it may not otherwise. For example: fn main() { let world = gives_string().split(" ").next(); println!("{:?}", world); } fn gives_string() -> String { String::from("hello world") } This will fail because the String is temporary, and we're trying to get a reference t…

This has surprised me on occasion. Shouldn't the compiler be smart enough to figure out how long to keep it around for, instead of failing to compile?

One could argue that the compiler could transform this for you. But systems languages are also about control. For someone versed in the way things are supposed to work, an owned value living longer than it should is surprising.

Tradeoffs, tradeoffs.

Re: Update on await syntax in Rust

#124
post #119

Earlier quoted context omitted.

I as well think there could be promise in someday exploring that space, but for the moment my enthusiasm for hypothetical postfix macros (which have yet to ever be formally proposed) is somewhat dampened by the realization that `foo.bar.qux.qaz.await!()` would need to expand to `await { foo.bar.qux.qaz }`, which makes me consider how uncomfortable such macros would be to parse (the saving grace of "normal" macro call…

> `foo.bar.qux.qaz.await!()` would need to expand to `await { foo.bar.qux.qaz }` Interesting point, thank you. I hadn't seen this previously mentioned, and it's definitely a reasonable argument. > there's no denying that `foo.await?.bar` appears nicer than `foo.await!()?.bar`, especially if there is no guarantee that postfix macros will ever become a thing. Agreed, for sure. I'm honestly just very concerned about the…

No problem. :) I was actually in the same camp as you until, in the wake of boats' prior post on await syntax, I sat down and got well into writing an RFC for postfix macros until I stumbled upon the parsing concern and shelved it under the category of "not nearly as trivial as I thought it would be".

Re: Update on await syntax in Rust

#125

Earlier quoted context omitted.

Yeah, I think the trick is that the theory and practice might be the same. And usually, it's the other way around; spreading it out makes it work, whereas it may not otherwise. For example: fn main() { let world = gives_string().split(" ").next(); println!("{:?}", world); } fn gives_string() -> String { String::from("hello world") } This will fail because the String is temporary, and we're trying to get a reference t…

This has surprised me on occasion. Shouldn't the compiler be smart enough to figure out how long to keep it around for, instead of failing to compile?

The compiler is smart enough to deduce lifetimes — that’s why it can throw a compile error — but fixing them (auto-keeping temporaries) might introduce extra memory consumption/leaks that the programmer did not intend. Requiring the programmer to explicitly assign a temporary to a variable makes the programmer’s intent more clear.

A comparison: in C++, the behavior of auto-extending the lifetime of const references to temporaries (but not non-const references) is considered a wart in the language design. (Really, you should just assign a temporary to a value because the compiler can elide the copy. : https://abseil.io/tips/101 .)

Re: Update on await syntax in Rust

#126
post #44

Awesome, thanks Rust team! I'm super excited for this to be stabilized. > That won’t be the end of the async/await feature - there will be a lot of extensions left out of the minimum feature Is there a summary of these somewhere for perusal?

i saw https://areweasyncyet.rs/ linked here, it mentions future extensions

Re: Update on await syntax in Rust

#127
post #60

Earlier quoted context omitted.

> Lazy vs strict semantics are completely orthogonal to the syntax [0] Alternatively, the syntax in Haskell just lends itself to lazy evaluation, and requires explicit annotation syntax to be strict. Contrasted with Python, which has a syntax that makes strict evaluation an easier default to express. If all languages can express, with some effort, the same exact semantics as any other language, then the only differen…

I'd submit the key difference in Haskell syntax is actually currying, not laziness. Haskell syntax privileges currying, and an executed function is just a curried function that has all of its parameters. By contrast, currying in languages with Algol-descended syntax always requires more rigamarole. It's possible, of course, in a lot of them, but it's harder than just a function call missing some of its arguments.

> By contrast, currying in languages with Algol-descended syntax always requires more rigamarole. It's possible, of course, in a lot of them, but it's harder than just a function call missing some of its arguments.

Really? Given a Python function

    def divide_by(a, b):
        return a / b
What would be so hard about

    divided_by_2 = divide_by(, 2)
    divide_5 = divide_by(5, )
Or, in Rust:

    fn divide_by(a: f32, b: f32) -> f32 {
        a / b
    }
    // ...
    let divide_by_2 = divide_by(, 2.0);
    let divide_5 = divide_by(5.0, );
I can't immediately see any major problems with this. It's basically a closure, after all:

    let divide_by_2 = |a| divide_by(a, 2.0);

Re: Update on await syntax in Rust

#128
post #88

A bit off-topic: Is there any theoretical reason you need async / await syntax at all? (It's certainly desirable for performance and compatibility to avoid making all subroutines into coroutines, so I understand why most languages have done this.) And restricting it to the case where coroutines have a single return... Subroutines are naturally coroutines that don't yield. And it seems like the question of whether it'…

Indeed, I think going forward we will see high-level languages that paper over the distinction between synchronous and asynchronous functions. However for Rust specifically, the performance implications you note are of particular concern, while the remark "it seems like the question of whether it's a subroutine or a coroutine shouldn't be something the programmer needs to worry about" is debatable when considering languages that aim to sit at the same level of the stack as C.

Re: Update on await syntax in Rust

#129
post #69
post #52

Earlier quoted context omitted.

I think you can skip the empty () in D. The syntax becomes much more readable when you don't have to return to upper levels of functions. Thankfully pipes exist in functional languages, which makes it feel just right half_square = a |> square |> divide(_, 2)

I left the `()` because in some imperative languages there's a difference between `() -> a` and `a`, which makes `a.b` and `a.b()` different. In Haskell there's `>>>` and `&` in base, which I use all the time for this sort of workflow: find_half_square = square >>> (flip divide) 2 This creates a function which takes a value, performs `square`, then performs `(flip divide) 2`. It's written in pointfree style, which me…

"(flip divide) 2" can also be written as "(`divide` 2)" – i saw that idiom recently and i'm warming up to it!

Re: Update on await syntax in Rust

#130

Question for people more familiar with async/await semantics, how do you typically control what thread the async procedure runs on? Having spent a lot of time now in rxJava and really getting into the power of stream processing and composition, it feels like async/await is almost too simplistic.

In C#, this is defined by the current thread's synchronization context.

If you're writing a console application, the main thread doesn't have one. When an async function wants to resume, it will usually resume on some thread pool's thread. Potentially different one each time. Can be any other thread too, the runtime just resumes running the function on the same thread which completed the await.

But if you're writing a GUI app and launching an async function from its GUI thread, that thread has a current sync.context. When the function will want to resume running or fails, the runtime will resume/raise exception on the same thread where it started. More precisely, the runtime delegates the decision to the sync.context, and the contexts set by GUI frameworks choose to resume on the GUI thread.

This may cause some funny deadlock bugs, but in most cases works surprisingly well in practice.

Also it's easy to implement custom synchronization contexts if needed.

Post reply on HN