Rust async is a bit different than in other languages. It's more like sugar over state machines instead of sugar over callbacks.
This is what makes it work nicely on embedded. The compiler-generated state machines are structs with fixed size so they can be statically allocated. Callbacks would have to be heap-allocated and garbage-collected/refcounted.
> It's more like sugar over state machines instead of sugar over callbacks
they are equivalent [1]. There are scheme compilers (a language with have first class continuations and often heap allocated stack frames) that compile everything down to a giant C switch statement.
[1] well, continuations are strictly more powerful of course, but the stackless subset needed for async/await is the same.
This misses the actual async part, which is more like polling a task queue. Callback hell sugar is only a thing in langues that already have event loops built in (i.e. JavaScript, which I assume is also what you're referring to)
The point is that it actually is insightful to think of it as callback sugar. That will give you better understanding of how the threading is handled when the calling method yields to the callee and conversely how it must be different when the caller isn't yielded.
It generally applies to eventloops, not just to lanugages with a builtin event loop. E.g. it certainly also applies to raw boost::asio which uses callbacks, libuv, libevent, QT, GObject, Netty, etc.
There are no threading implications to async in Rust. The executor you're using may add some requirements on your futures because it wants to run them on multiple threads[1], but that's not related to async itself, and you can always use a single threaded executor if you don't want these limitations (and they doesn't apply to embeded anyway).
[1] namely, your futures will need to be Send + 'static.
I'm not as familiar with Rusts's implementation but even in C# it's mostly true. Threads are only hit when a callback is not directly awaited. There's more to it but it starts you down the right path, I think.
This is what makes it work nicely on embedded. The compiler-generated state machines are structs with fixed size so they can be statically allocated. Callbacks would have to be heap-allocated and garbage-collected/refcounted.
(disclaimer: Embassy maintainer here)