I don't know if this applies, but I read somewhere recently (perhaps /r/rust) something along the lines that Tokio allows you to use a single-threaded executor and that effectively relaxes some code requirements.
I have seen the single threaded tokio runtime, however I don't totally see how that helps. From what I can tell Tokio runtimes are meant to be swapped in and out, meaning that from a compiler perspective it has to assume that each task may be run in it's own thread. Since rust only lets one function have a mutable instance of a value at one time you can't share that mutable instance across all your different tasks (even if they are all inlined via closures the compiler still can't prove who will have mutable access at any given time).
So sure I could use RefCell<T> but there's no way to prove that all borrow calls are scoped correctly all over the place so that a mutable borrow doesn't panic your whole system down. Even with a single threaded runtime I can see this causing surprises.
I did get up and running with Mio for a base but I feel like I should tackle my confusion head on and just try to figure out how to make Tokio or async-std work. So far the conclusion I've come to is I need to treat each high level async function as an actor and use the actor model with channels for message passing between them. This is the only logical conclusion I can see to make this work in a somewhat manageable fashion.