4 ms·
> This can be optimized away but is not guaranteed. Async/await will not allocate in Rust. That makes Rusts solution sound better. But in reality things are a
by Matthias247 6y ago
> This can be optimized away but is not guaranteed. Async/await will not allocate in Rust.
That makes Rusts solution sound better. But in reality things are a bit more differentiated. Both solutions are not zero cost abstractions for various use-cases. And depending on your use-case one or the other solution might end up being better.
From the user perspective, a big difference is that Rusts `Future`s are lazy (will only do something when polled), while C++ coroutines and the `task` frameworks on top of them are not. Besides influencing the way concurrency can be performed within these environments, this e.g. also has an impact on the stack usage of the solution (Rusts Futures can require a terrible amount of stack just to create the final composed Future).
Then there is the "interface" question. As far as I understand you can have virtual coroutine functions on classes the same way that you can an other functions. But you can't have the same in Rust at the moment. The "async-trait" crate gives you that ability again - but with the drawback that you now have a mandatory allocation (instead of the often elided one in C++).
Overall C++ coroutines are a lot more flexbile than Rusts async/await. The behavior can specified by overriding the promise type, storage type, behavior of co_await, etc. You can make a generator out of it, but also an async function. Rusts async functions are targetted purely at the async function use-case - which might make them a bit easier to understand.
Even within that use-case they are more constrainted. E.g. with C++ coroutines you can do things like "thread-hopping" - `co_await method_that_moves_me_to_other_thread()`. Rusts executor idea wants you to resume your piece of work on the same task (which imho think is nearly always a better idea!).
Another difference is that Rusts "resume/Waker" mechanism is designed for a multithreaded environments - it will take a needless performance hit in a pure singlethreaded shared-nothing environment(think Seastar). With the C++ solution you might be able to avoid that - but at the cost of the framework and language not providing you any thread-safety at all.
- gpderetta 6y agostd::future/promise is hopelessly inefficient, but the new executor framework being proposed for C++23 does provide for lazy evaluation of futures (or its equivalent). In fact lazy evaluation is the default. Executors can interface with coroutines (although the impedence mismatch is real) and the interface is powerful enough that allocation can be avoided in many cases (not via compiler optimization which I find too fragile), but via being able to inplace construct the coroutine frames in other existing regions of memory. I was very critical of the C++ coroutine design as the extension interface feels very ad hoc and not built from first principles and because of the hard to avoid memory allocation, but people that know better than me seem to think that it is usable in practice with good performance. I do believe that thread hopping is quite important in case you need access to remote data and want to avoid mutual exclusion.