In Rust, the state machines just implement one trait, Future, which has one method: poll.
For a very long time, async/await were just normal Rust macros; there was no compiler<->library interface.
For a year or so, async/await are proper keywords, which provides nicer syntax, and some optimizations that were hard to do with macros (e.g. better layout optimizations for the state machines).
But that's essentially the whole thing.
Looking at safety, flexibility, performance and simplicity, Rust design picks maximum safety, performance, and simplicity, trading off some flexibility in places where it really isn't necessary:
- you can't move a coroutine while its being polled, which is something you probably shouldn't be doing anyways
- you can't control the layout of the coroutine state for auto-generated corotuines; but you can lay them out manually if you need to, for perf (the compiler just won't help you here)
- you need to manually lay out a coroutine and commit to the layout for using them in ABIs
C++ just picks a different design. 100% safety isn't attainable, maximum flexibility is very important, performance is important, but if you need this you have alternatives (callbacks, etc.). I personally just find the API surface of C++ coroutines (futures, promises, tasks, handles, ...) to just be really big.