Live data from Hacker News

Inside Rust's Async Transform

blag.nemo157.com

11–20 of 84 posts

Re: Inside Rust's Async Transform

#11

> is very different to other well-known implementations (C# and JavaScript [...]). Instead of performing a CPS-like transform where an async function is split into a series of continuations that are chained together via a Future::then method, Rust instead uses a generator/coroutine transform to turn the function into a state machine. C# async/await is also very much resumable state machines

You can use ILSpy and disable async decompilation to see that C# creates a state machine.

Re: Inside Rust's Async Transform

#12
post #9

Earlier quoted context omitted.

One difference that may exist is that in Rust, async fns don’t immediately execute, they simply create one of these values. I forget if JS and C# do something different, that is, the execute up until the first suspend point. This was one of the major design decisions we’ve made that’s different than other languages.

No you're correct, C# async/await synchronously executes up to the first yield point. In general the tasks are 'hot' in C#, as opposed to F#'s 'cold' Async type.

That’s true for `async` methods, but keep in mind that you can `await` any value in C# that implements the ‘awaitable’ pattern. A custom ‘awaitable’ can perform its own scheduling however it likes, including when it begins its execution.

The pattern-based implementation of `await` is, in my view, the coolest part of the async/await feature set.

Re: Inside Rust's Async Transform

#13
post #9

Earlier quoted context omitted.

One difference that may exist is that in Rust, async fns don’t immediately execute, they simply create one of these values. I forget if JS and C# do something different, that is, the execute up until the first suspend point. This was one of the major design decisions we’ve made that’s different than other languages.

No you're correct, C# async/await synchronously executes up to the first yield point. In general the tasks are 'hot' in C#, as opposed to F#'s 'cold' Async type.

Though F# Async is quite a bit different in that it is the computation rather than a cold invocation. One can use Tasks in F# of course but last time I checked, it will only use a CPS transformation rather than the more efficient state machine representation C# has (F# does use state machines for seq expressions so it’s not a fundamental limitation but more a matter of work).

Aside: This can be a source of errors in F# code I’ve seen as folks will mix up the cases where they want to result vs running a computation many times. There certainly is plenty of expresivity but in practice it’s a muddier representation (it took me over a year to appreciate this).

Re: Inside Rust's Async Transform

#14
post #9

Earlier quoted context omitted.

No you're correct, C# async/await synchronously executes up to the first yield point. In general the tasks are 'hot' in C#, as opposed to F#'s 'cold' Async type.

That’s true for `async` methods, but keep in mind that you can `await` any value in C# that implements the ‘awaitable’ pattern. A custom ‘awaitable’ can perform its own scheduling however it likes, including when it begins its execution. The pattern-based implementation of `await` is, in my view, the coolest part of the async/await feature set.

C++ follows up on the same idea.

Re: Inside Rust's Async Transform

#15
post #3

Doesn't both JS (via Babel) and C# implement asynchronous functions as state machines in a similar fashion?

Yes, Babel's transform of `async` to ES5 is similar. It turns has to turn a function into a state machine. Rust goes a step further and turns the entire chain of futures into one large combined state machine.

But the rest of the event-loop machinery is quite different. JS's async is still fundamentally callback-based. Rust's futures are polled. In JS there's a single global event loop and promises run automagically. In Rust you create futures managed by their executors, each handling its own kind of tasks (CPU pools, network polling).

Re: Inside Rust's Async Transform

#16

> is very different to other well-known implementations (C# and JavaScript [...]). Instead of performing a CPS-like transform where an async function is split into a series of continuations that are chained together via a Future::then method, Rust instead uses a generator/coroutine transform to turn the function into a state machine. C# async/await is also very much resumable state machines

Same for Kotlin.

Re: Inside Rust's Async Transform

#17
post #3

Doesn't both JS (via Babel) and C# implement asynchronous functions as state machines in a similar fashion?

One difference that may exist is that in Rust, async fns don’t immediately execute, they simply create one of these values. I forget if JS and C# do something different, that is, the execute up until the first suspend point. This was one of the major design decisions we’ve made that’s different than other languages.

Just for comparison, Dart started out with async functions that suspended immediately, but in Dart 2, they switched to running synchronously to first await for performance (fewer unnecessary suspends) and to avoid race conditions.

Without this, you sometimes had to write a write a wrapper function that does some synchronous setup and returns a Future, which was a bit annoying for stylistic reasons.

There's an interesting but somewhat old discussion here:

https://www.reddit.com/r/rust/comments/8aaywk/async_await_in...

I wonder if anything changed since then? I'm not a Rust programmer so I didn't really understand the article.

Re: Inside Rust's Async Transform

#18

> is very different to other well-known implementations (C# and JavaScript [...]). Instead of performing a CPS-like transform where an async function is split into a series of continuations that are chained together via a Future::then method, Rust instead uses a generator/coroutine transform to turn the function into a state machine. C# async/await is also very much resumable state machines

Tried to clarify this, it's not that C# and JS are implemented as CPS-like, rather that that's how they're commonly explained; and because of the GC this difference is not really externally visible, whereas with borrowing in Rust it would be.

Re: Inside Rust's Async Transform

#19
post #15
post #3

Doesn't both JS (via Babel) and C# implement asynchronous functions as state machines in a similar fashion?

Yes, Babel's transform of `async` to ES5 is similar. It turns has to turn a function into a state machine. Rust goes a step further and turns the entire chain of futures into one large combined state machine. But the rest of the event-loop machinery is quite different. JS's async is still fundamentally callback-based. Rust's futures are polled. In JS there's a single global event loop and promises run automagically.…

Could generating a single combined state machine result in code bloat? (Similar to inlining.)

Re: Inside Rust's Async Transform

#20
post #6

I got lost in the weeds fairly quickly with this blog post, why is it that you didn't have to implement Future? Pinning is required here because your AsyncRead read_to_end returns a future bound by some reference lifetime?

The actual Future implementation comes from std, std::future::from_generator takes a generator with the right associated types and turns it into a future.

Yep, the generator created by quote_encrypt_unquote is creating internal self-references from the future created by read_to_end into the AsyncRead it's storing in its environment, while this is happening the AsyncRead must not move and therefore the generator must not move, which is what pinning represents.

Post reply on HN