Rust gets it right, but has its own warts, especially if you're coming from async in a GC world. But there's no allocation; Futures are composable value types.
Rust gets it right, but has its own warts, especially if you're coming from async in a GC world. But there's no allocation; Futures are composable value types.
I wouldn't say that applies to everybody. I use C++ because it interfaces with the system libraries on every platform, because it has class-based inheritance (like Java and C#, unlike Rust and Zig) and because it compiles to native code without an external runtime. I don't care to much about allocations.
For me the biggest fumble is that C++ provides the async framework, but no actual async stdlib (file io and networking). It took a while for options to be available, and while eg Asio works nicely it is crazily over engineered in places.
Zig, is really Modula-2 in C's cloathing, I don't like the kind of handmade culture that has around it, and its way of dealing with use after free I can also get in C and C++, for the last thirty years, it is a matter of actually learning the tooling.
Thus C++ it is, for anything that isn't can't be taken over by a compiled managed language.
I would like to use D more, but it seems to have lost its opportunity window, although NASA is now using it, so who knows.
It is the same principle that drives languages like Rust in regards to being safe by default, in theory stuff like bounds checks cause a performance hit, in practice compilers are written to elide as much as possible.
I say this as someone that is not a fan of the stackess coroutines in general, and the C++ solution in particular.
C++ coroutines are so lightweight and customizable (for good and ill), that in 2018 Gor Nishanov did a presentation where he scheduled binary searches around cache prefetching using coroutines. And yes, he modified the allocation behavior, though he said it only resulted in a modest improvement on performance.
void *operator new(std::size_t sz, Foo &foo, Bar &bar) { return foo.m_Buffer; /* should be std::max_align_t-aligned \*/ }
and force all coroutines of your Coroutine type to take (Foo &, Bar &) as arguments this way (works with as many overloads as you like).