Basically, just like C# (and VB.NET and C++ .NET task/then) provides syntax and semantics for async-await, Rust provides it at language level too. (And it defines how the compiler transforms it into Future objects.)
But, since Rust doesn't have a mandatory runtime, something needs to implement the low-level stuff that knows what to do with these Future objects. (In Tokio you have a work-stealing threadpool, but maybe in smaller runtimes you don't need all that fancy stuff for high-throughput, you just need small binary size, so there's a runtime/library called "smol" that's main feature is that it's a small async runtime.) In the CLR as far as I know there are Task objects, which basically correspond to Rust's Future objects.
One interesting low-level difference (similarity?) is that in the CLR there's an explicit callback support by the runtime (to wake up Task objects - which can lead to deadlocks if they are scheduled on the UI thread), whereas in Rust Futures pass their own callbacks (called Waker) to a thing called the Reactor (which is basically the low-level implementation of the Executor, which binds to the OS/kernel level primitives, such as epoll or IOCP).
And even though it's a "zero cost" abstraction, it still means there's a state machine, just like in .NET. Except it's built and "deadlock checked" at compile time.
https://tooslowexception.com/wp-content/uploads/2020/05/ther...
https://www.red-gate.com/simple-talk/dotnet/net-framework/th...
You might be able to argue that its even more pluggable than Tokio because the system has the concept of current context and attaching a task to it. Library code can use your custom scheduler.
You can even throw away the Task type and create your own future type that works with the async/await syntax but of course no library would be able to pick that up.