Live data from Hacker News

How Rust optimizes async/await

tmandry.gitlab.io

61–70 of 130 posts

Re: How Rust optimizes async/await

#61
post #58

I'm a big user of Rust, but I'm kinda dismayed that async IO wasn't part of the original design. It's nice they're making Futures a zero-cost abstraction, but it feels like it is at the expense of ergonomics.

You have to prioritize something. Development of other features was prioritized over async first. Futures support came later, and async later still. It's an iterative approach. In the end, it let people get a LOT of value out of rust well before async was fully baked.

For myself, I'd been waiting for the syntax to finalize as I'm still learning and didn't want to really delve into old and new ways, though I'm sure I will see them in practice. For others, depending on your needs, if you didn't need it, or willing to jump through the hoops, you could still get value along the way.

Re: How Rust optimizes async/await

#62
post #21
post #14

Earlier quoted context omitted.

You need a third-party library to provide an executor. Rust does not come with one to keep the runtime size small. The community seems to have centralized on https://tokio.rs/ (under the hood it uses epoll/whatever OS-specific functionality to schedule M:N) See: https://news.ycombinator.com/item?id=20722297

How's that play out in terms of, say, performing some long-running calculations, rather than something that performs IO?

You're essentially implementing statically scheduled cooperative userspace threads on top of a single thread with async/await.

So, for computation, this is a bad solution.

Re: How Rust optimizes async/await

#63
post #59
post #56

Does anyone know how Rust's implementation compares to C++2a's? The C++ people seem to have spent a lot of time creating an extremely generic framework for async/await wherein it is easy to change out how the scheduler works (I currently have a trivial work stack, but am going to be moving to something more akin to a deadline scheduler in the near future for a large codebase I am working with, which needs to be able…

Disclaimer: I'm not an expert on the proposal, but have looked at it some, and can offer my impressions here. (Sorry, this got a bit long!) The C++ proposal definitely attacks the problem from a different angle than Rust. One somewhat surface-level difference is that it implements co_yield in terms of co_await, which is the opposite of Rust implementing await in terms of yield. Another difference is that in Rust, all…

Thanks for the information!!

On your last paragraph, the thing I'm concerned by is where this extra priority information is stored and propogated, as the term "task" is interesting: isn't every single separate thing being awaited its own task? There isn't (in my mental model) a concept that maps into something like a "pseudo-thread" (but maybe Rust does something like this, requiring a very structured form of concurrency?), which would let me set a "pseudo-thread" property, right?

As an example: if I am already in an asynchronous coroutine and I spawn of two asynchronous web requests as sub-tasks, the results of which will be processed potentially in parallel on various work queues, and then join those two tasks into a high-level join task that I wait on (so I want both of these things to be done before I continue), I'd want the background processing done on the results to be handled at the priority of this parent spawning task; do I have to manually propagate this information?

In C++2a, I would model this by having a promise type that is used for my prioritize-able tasks and, to interface with existing APIs (such as the web request API) that are doing I/O scheduling; I'd use await_transform to adapt their promise type into one of mine that lets me maintain my deadline across the I/O operation and then recover it in both of the subtasks that come back into my multi-threaded work queue. Everything I've seen about Rust seems to assume that there is a single task/promise type that comes from the standard library, meaning that it isn't clear to me how I could possibly do this kind of advanced scheduling work.

(Essentially, whether or not it was named for this reason--and I'm kind of assuming it wasn't, which is sad, because not enough people understand monads and I feel like it hurts a lot of mainstream programming languages... I might even say particularly Rust, which could use more monadic concepts in its error handling--await_transform is acting as a limited form of monad transformer, allowing me to take different concepts of scheduled execution and merge them together in a way that is almost entirely transparent to the code spawning sub-tasks. The co_await syntax is then acting as a somewhat-lame-but-workable-I-guess substitute for do notation from Haskell. In a perfect world, of course, this would be almost as transparent as exceptions are, which are themselves another interesting form of monad.)

Re: How Rust optimizes async/await

#64
post #63
post #59

Earlier quoted context omitted.

Disclaimer: I'm not an expert on the proposal, but have looked at it some, and can offer my impressions here. (Sorry, this got a bit long!) The C++ proposal definitely attacks the problem from a different angle than Rust. One somewhat surface-level difference is that it implements co_yield in terms of co_await, which is the opposite of Rust implementing await in terms of yield. Another difference is that in Rust, all…

Thanks for the information!! On your last paragraph, the thing I'm concerned by is where this extra priority information is stored and propogated, as the term "task" is interesting: isn't every single separate thing being awaited its own task? There isn't (in my mental model) a concept that maps into something like a "pseudo-thread" (but maybe Rust does something like this, requiring a very structured form of concurr…

In a Rust implementation of your example, you might not necessarily spawn the two sub-operations as separate tasks. Awaiting them directly in a parent async function (probably using FuturesUnordered, like Promise.all in JS) will cause all of their work to be scheduled, prioritized, cancelled, etc. together because they’ll be a part of the same task. There’s a 1-many relation from tasks to Futures in Rust.

Re: How Rust optimizes async/await

#65
post #26

Earlier quoted context omitted.

See these examples of how async/await simplifies looping and error handling: https://docs.rs/dtolnay/0.0.3/dtolnay/macro._01__await_a_min... . Await syntax makes it much easier to compose asynchronous code from different libraries, in the same way that ordinary function calls compose synchronous code. Talking to epoll/mio directly is certainly possible, but it's hard to make two different libraries work well together…

Yeah but you’re talking about things not even stable. Old tokio with closures is terrible imo.

It’s extremely close to being stable; it just missed a release train, because there’s some last minute polish that needs to be done. It will be on its way soon!

Re: How Rust optimizes async/await

#66
post #5

As a newcomer to Rust, wishing that this post was one of the first ones I've read about this topic. It took scouring through many many posts, some of them here on HN, to be able to grasp some of the same idea. (I may not be alone, judging from the very long discussion the other day: https://news.ycombinator.com/item?id=20719095 )

Part of this is just that the design has been in the works for four years and has changed significantly during that time; it’s only now that things are almost stable that it’s worthwhile to write these kinds of things.

Re: How Rust optimizes async/await

#67
post #63
post #59

Earlier quoted context omitted.

Disclaimer: I'm not an expert on the proposal, but have looked at it some, and can offer my impressions here. (Sorry, this got a bit long!) The C++ proposal definitely attacks the problem from a different angle than Rust. One somewhat surface-level difference is that it implements co_yield in terms of co_await, which is the opposite of Rust implementing await in terms of yield. Another difference is that in Rust, all…

Thanks for the information!! On your last paragraph, the thing I'm concerned by is where this extra priority information is stored and propogated, as the term "task" is interesting: isn't every single separate thing being awaited its own task? There isn't (in my mental model) a concept that maps into something like a "pseudo-thread" (but maybe Rust does something like this, requiring a very structured form of concurr…

A “task” is the thing you submit to an executor, that is, the sum of all async/awaits in a single computation. Each await is not its own task.

Re: How Rust optimizes async/await

#68
post #60
post #58

I'm a big user of Rust, but I'm kinda dismayed that async IO wasn't part of the original design. It's nice they're making Futures a zero-cost abstraction, but it feels like it is at the expense of ergonomics.

Futures were developed outside Rust core, in a third-party library, before being brought into the language. Working with them in combinator form definitely was less ergonomic, but async/await fixes that.

Fun fact: there was a future type in the rust standard library, long, long ago.

https://doc.rust-lang.org/0.10/sync/struct.Future.html

Re: How Rust optimizes async/await

#69
post #59
post #56

Does anyone know how Rust's implementation compares to C++2a's? The C++ people seem to have spent a lot of time creating an extremely generic framework for async/await wherein it is easy to change out how the scheduler works (I currently have a trivial work stack, but am going to be moving to something more akin to a deadline scheduler in the near future for a large codebase I am working with, which needs to be able…

Disclaimer: I'm not an expert on the proposal, but have looked at it some, and can offer my impressions here. (Sorry, this got a bit long!) The C++ proposal definitely attacks the problem from a different angle than Rust. One somewhat surface-level difference is that it implements co_yield in terms of co_await, which is the opposite of Rust implementing await in terms of yield. Another difference is that in Rust, all…

It's interesting that support for recursion is no longer the default here. A partial reversal of what happened going from Fortran to Algol?

Re: How Rust optimizes async/await

#70
post #8

Earlier quoted context omitted.

Not necessarily. They're an implementation detail of the compiler, and aren't fully baked yet to boot. But there's plenty of reason to want generators, including the fact that they let you build streams. And the fact that async/await relies heavily on them has pushed the implementation much closer to being ready. I hope we get them at some point!

They are available in nightly - https://doc.rust-lang.org/beta/unstable-book/language-featur... - but are very unstable. I think "at some point" is the best answer :)

To be clear on how unstable: they have not received an actual RFC yet, so they’re barely designed at all. It will be some time before they’re stable, as in some sense, they haven’t even started the process.

I’d imagine that they would go through the stages faster than many other features, though, given that they implementation will have received a lot of testing via async/await.

Post reply on HN