Hacker News new | ask | show | jobs
by RedPanda250 1219 days ago
Sorry if I'm missing something important as I have experience with C++ but not rust, but is async/await the right abstraction in Rust vs. the underlying generators which I assume is lower level and closer to C++20 Co-routines in terms of performance ? [1]

In other words, it seems Rust co-routines at the level of Tokio are not as performant as C++ native Co-routines [2] and further down the thread, it's mentioned that once generators are stabilized, it might help build lower cost and performant stackless coroutines in Rust.[3]

[1] https://github.com/rust-lang/rust/issues/43122#issuecomment-...

[2] https://github.com/rust-lang/rust/issues/43122#issuecomment-...

[3] https://github.com/rust-lang/rust/issues/43122#issuecomment-...

1 comments

Hmm… I'd say for IO-bound tasks/loading in a DBMS yes, async/await coroutines are the correct abstraction, since you're waiting to reschedule on external notice and normally don't want to spin-wait (or you could block on the optimistic case and then `.await` the fallback).

If you'd like to do modular data processing instead then the current best Rust approach is using `Iterator`s[1] and possibly the `rayon`[2] crate for safe parallelisation.

There currently is no coroutine feature that maps nicely onto `Iterator`s, or rather that's indeed expected to be the unstable generators feature, as it should cover that use case without overhead. (Async-coroutines map onto `Future`[3] instead.)

You can think of Rust's `async` and generator use cases vaguely like the ones of JS's async and generator functions. The latter will function without any runtime or trampoline if you advance it in a loop, as far as I can tell, but they're unwieldy if what you want to yield on are only waits on external events. (`Waker`s[4] have similar consumer semantics to JS's or C#'s continuation passing, even if they are only indirect memory management handles.)

It also often makes sense to use both of these at the same time to produce something like an `AsyncIterator`[5], but I expect that feature to be quite some ways away in Rust, at least in a seamlessly mixing fashion.[6]

- - -

I'm actually pretty curious which will turn out faster in practice between C++'s coroutines and Rust's generators, since their memory management is so different.

The Rust coroutines (that is: including `Future`s) are generally stackless already, but also by default heapless, as their entire memory is abstracted as opaque instance with anonymous type that is returned to the caller by value. Boxing them is an explicit operation, and it's equally possible to pin them on the current stack/in the current instance instead. (That's how `.await` works; the child is embedded into the outer `Future` directly.)

There are some pitfalls related to this, since that instance's size is determined by the largest `.await`/yield site, but the tooling should (eventually) be able to warn on unexpectedly large `.await`/yield locations like it currently warns on unexpectedly large enum variants.[7]

(I'll borrow your footnote style.)

[1] https://doc.rust-lang.org/stable/core/iter/trait.Iterator.ht...

[2] https://crates.io/crates/rayon

[3] https://doc.rust-lang.org/stable/core/future/trait.Future.ht...

[4] https://doc.rust-lang.org/stable/core/task/struct.Waker.html

[5] https://doc.rust-lang.org/stable/core/async_iter/trait.Async...

[6] then comparable to JS, I'd assume, where `yield` and `await` aren't necessarily paired: <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...>

[7] https://rust-lang.github.io/rust-clippy/stable/index.html#la...

Thank you for the very detailed response. I'll check the links out.