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]
Coroutines in Rust are a native language feature and their API is part of `std`, but the executors indeed come from community libraries.
Tokio is one option, but as long as you don't contextually fork (for parallelism) you can in theory run the same coroutines on any other executor, like the ones from async-std.
Spawning a sibling task (as opposed to mixed child task polling, which can be macroed inline) unfortunately isn't available through the common API so far, which is where the bulk of the mentioned incompatibilities stems from.
Rust coroutines evaluate to opaque value types instead of handles, for comparison, so the caller gets to decide what to do with them.
Currently they're a bit unwieldy though, until naming (aliasing) their types becomes possible. The unstable documentation for that is here: https://rust-lang.github.io/impl-trait-initiative/explainer/...
(Edit: Sorry, I'd grabbed the wrong second link. Fixed.)
They aren't strictly comparable. While the heap allocation may be optimised out, there is no way to use native C++ coroutines as guaranteed zero-cost construct due to the automatic heap allocation in the API.
Rust's coroutines currently have practical optimisation issues related to instance size (because the compiler isn't smart enough yet in a few places), and the more powerful generator API very much isn't ready yet, but they give you considerably more control over the memory management.
It's easy to stack- or pool-allocate coroutines there if necessary for performance, while in C++ you wouldn't be able to use the native language feature where frequent or unmonitored heap allocations from fine-grained coroutine use would be a problem. (Apparently this makes them unsuitable for AAA game development, for example.)
Library-provided coroutines may be more versatile.
I should. I'm not too familiar with the process so far, but I'll find some time for it.
These functions are indeed generally sound (also for `Rc`), as any value that cares would be `!Unpin`, which would still bar access to `&mut T`.
`make_mut_pinned` also shouldn't cause too much confusion, as availability of `Clone` would have to be declared explicitly just about everywhere that's relevant.
They can be implemented as very thin wrappers around their non-pinning equivalents, with only a few `unsafe` operations to make the types fit.
There's actually a direct mention of that in my post already, but it's all the way near the bottom in the "Weakening non-moveability" section. It probably goes a bit too far to link in the intro, so I inlined a quick definition as relative clause there instead.
The `moveit` library is interesting also because it would allow pinning containers to expose a wider mutable API, though there's currently no trait to update instances post-move, which would allow `realloc` use. I filed an issue for it about a week ago: https://github.com/google/moveit/issues/26
That still leaves the issue of callback targets though, which would have to be notified before a possible move to support one-step reallocation.
Since it turned out quite a bit longer that way, I was pretty liberal with the bold formatting to make it still easy to skip for those familiar with the issue.
Something else that I didn't mention in the post is that `Pin<Box<T>>` is `From<Box<T>>` (so it can be cast directly, and here without reallocation or such), and this could also be implemented for (other) exclusively-owning containers with heap storage.
The standard library collections don't do this because they don't have an API that would make it useful.
It suspect it's partly because you can't `self: &mut Pin<Self>` in stable, which would be needed to cleanly give them one, but there's also a fair argument for not making them kitchen-sink types while a wrapper could provide this easily.
, which for pinning-aware values should give you enough mutability and for `T: Unpin` can give you `Option<&mut T>` through `Option::as_deref_mut`.
Unfortunately, I haven't seen any `Arc` implementations that actually provide it, since almost none of the third-party ones are pinning-aware. (My `tiptoe::Arc`'s `get_mut` has this signature, but that's a specialty container with additional requirements for the contained value.)
The same goes for `make_mut_pinned`, that's missing too (but to be fair would be much less useful, since pinning and `Clone` don't often mix that well).
This is why I insist on never calling it "pinned pointer" in the post, even if not explicitly.
I'm aware the term is used in the standard library docs (for context: `Pin`'s tagline is "A pinned pointer."), but in my eyes that's a misnomer 99% of the time.
It might be a good idea to split the wording thoroughly and officially (into "pinned" vs. "pin"/"pinning …"), since this is such a common sticking point and seemingly mixing concepts right now. (I.e. "pinned pointers" are (usually) "`Pin`-wrapped" but not "pinned in place". It only becomes more muddled once you have inherently-pinning or "add-on" pins without `Pin` in their type.)
"Trivially movable" means anyone can take an instance (that they own) and copy its data (just the plain memory cells) to a new location with a new address, then continue to use that instance at that new location without breaking anything.
This is a weaker guarantee than `Copy` because they're not necessarily allowed to use both copies of the data as true instances. They can still delay choosing one, but they can only ever interact with one of them through its API.
I'll see if I can add a link to an explanation there. In Rust it's mostly a base assumption and many other languages (C#, Java, JS…) avoid the topic almost entirely, so they all don't explain it very prominently.
C++ is much more explicit about it, but unfortunately calls this property a bunch of different names. (I've seen "trivially relocatable" as general term for it, since a "move" seems to be a very specific action in C++.)
The intro section turned out a bit C++-y, maybe. You can read up on the terms by clicking any underlined text. I'm not too happy about links not being blue, but I can't change it without being in the Hashnode ambassador program. (Not much spare money means my entire web presence is strung together from free or very inexpensive platform services. Maybe I'll post about that in the future.)
You can also skip the "The Problem" section entirely, though. It's not that important overall and serves mostly as on-ramp for users of other languages with manual memory management.
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...