Live data from Hacker News

Inside Rust's Async Transform

blag.nemo157.com

21–30 of 84 posts

Re: Inside Rust's Async Transform

#21

> 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

Yes, the state machine generation aspect is similar.

However the execution aspect is a bit different: In C#, once a leaf future/Task gets resolved, it will in many cases sychronously call back into the state machine which awaited the task Task (by storing a continuation inside it). A whole promise chain might resolve synchronously directly on the stack of the caller. And "in many cases", because the whole thing depends on some very subtle properties like whether a SynchronizationContext or TaskScheduler was configured.

In Rusts task system a leaf future will never call back into the parent. It will always only notify the associated task executor, that it can retry running/polling the Future to completion. When the task gets executed again, it will run again from the normal scheduler thread in a top-down fashion.

This makes Rusts system a little less performant for some use-cases, but also a lot less error-prone (no synchronization issues because it's not known where some code runs). It also is one of the key ingredients for avoiding allocations on individual futures.

Javascripts system is closer to the C# mechanism, but avoids the error-prone part: When a leaf future is finished, it will lead to calling the continuation of the parent future. However this is never done synchronously, but always on a fresh iteration of the eventloop (to avoid side effects). That works fine for Javascript because the eventloop is guaranteed (it's not in C# async code), and Futures are on the heap anyway.

Re: Inside Rust's Async Transform

#22

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.

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 interes…

Yes, that was a big bit of feedback I was very happy to see.

I don’t 100% remember, but I think some of the details for us still ended up significantly different. There are so many ways to implement this stuff...

Re: Inside Rust's Async Transform

#23

> 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

IDK about JS runtimes, but this is very similar to the approach by most JS transpilers.

They will use language-level generators if compiling to ES 2015, or user-land generators if compiling below that.

Re: Inside Rust's Async Transform

#24
Off topic, but I’d just like to point out how blindingly fast this site loads: it loads quite literally instantly for me (I’m on mobile so I can’t give precise figures) but I don’t think I’ve ever used a site that loads that fast ever before.

Is the website author here? What are you running server side that’s giving such great performance?

Re: Inside Rust's Async Transform

#25

Off topic, but I’d just like to point out how blindingly fast this site loads: it loads quite literally instantly for me (I’m on mobile so I can’t give precise figures) but I don’t think I’ve ever used a site that loads that fast ever before. Is the website author here? What are you running server side that’s giving such great performance?

Only two requests: favicon and html page itself. No front-end framework, no tracking. The only JS is the 10 lines necessary for the buttons in the upper right corner.

Re: Inside Rust's Async Transform

#26

Off topic, but I’d just like to point out how blindingly fast this site loads: it loads quite literally instantly for me (I’m on mobile so I can’t give precise figures) but I don’t think I’ve ever used a site that loads that fast ever before. Is the website author here? What are you running server side that’s giving such great performance?

What cokml19 said, it’s all about minimising the work. This is just a normal Jekyll site hosted on github pages, but with just minimal css and js inlined into the page. There are actually a few more lines of js hidden around the place, e.g. for adding the “play” buttons onto the code snippets, but it’s all simple library-less code.

Re: Inside Rust's Async Transform

#27
Async/await pattern always confuses me, someone please let me know if I get this right:

First, async/await does NOT mean "threading" or "multiprocessing" or "concurrency". It simply means "using a state machine to alternate between tasks, which may or may not be concurrent." Right?

Further, in Javascript, futures and async are utilized heavily because we so frequently need to wait for IO events (i.e.: network events) to complete, and we don't want to block execution of the entire page just to wait for a IO to complete. So the JS engine allows you to fire off these network events, do something else in the meantime, and then execute the "done" behavior when the IO is complete (and even in this case, we might not be concurrent, because ).

That makes sense to me.

But say I have written something in Rust that makes use of async/await. And say there is absolutely no IO or multithreading. Say I have some awaitable function called "compute_pi_digits()" that can take arbitrarily long to complete but does not do IO, it's purely computational. Is there any benefit to making this function awaitable? Unless I actually spawn it in a different thread, the awaitable version of this function will behave identically to if it were NOT awaitable, correct?

And one last idea: the async/await pattern is becoming so popular across vastly different languages because it allows us to abstract over concepts like concurrency, futures, promises, etc. It's a bit of a "one size fits all" regardless of whether you're spinning up a thread, polling for a network event, setting up a callback for a future, etc?

Re: Inside Rust's Async Transform

#28

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.

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 interes…

A Rust async function doesn't work like Dart 1- it doesn't suspend immediately (i.e. return back to the event loop to be scheduled), it just doesn't run to the first await until it's actually awaited or otherwise `poll`ed itself.

So the performance concern simply does not apply- Rust suspends the same number of times as Dart 2. The race condition concern might, depending on how you look at it, but in return the execution model from within a Future is much more straightforward and predictable.

Re: Inside Rust's Async Transform

#29

Async/await pattern always confuses me, someone please let me know if I get this right: First, async/await does NOT mean "threading" or "multiprocessing" or "concurrency". It simply means "using a state machine to alternate between tasks, which may or may not be concurrent." Right? Further, in Javascript, futures and async are utilized heavily because we so frequently need to wait for IO events (i.e.: network events)…

That's how I understand it.

Re: Inside Rust's Async Transform

#30

Async/await pattern always confuses me, someone please let me know if I get this right: First, async/await does NOT mean "threading" or "multiprocessing" or "concurrency". It simply means "using a state machine to alternate between tasks, which may or may not be concurrent." Right? Further, in Javascript, futures and async are utilized heavily because we so frequently need to wait for IO events (i.e.: network events)…

It would be misleading for consumers of the interface to mark compute_pi_digits awaitable -- at least in .NET world that have quite a lot of experience with async-await at this point.

See http://blog.ploeh.dk/2016/04/11/async-as-surrogate-io/ for further discussion. Regular programmers are not using Task as IO-monadic marker consciously, but they are surprised when a usage differs from that model.

Post reply on HN