Hm... really?
I was under the impression that it was more correct to say that mixing runtimes is undefined behaviour?
It may work, but just like mutable aliasing using unsafe may not cause memory corruption; it just happens to work in the current implementation, on the current platform, on the current version of rust.
There is no guarantee it will continue to work in the future.
Maybe I misunderstood, but I thought it was a lot more complicated than just starting multiple events loops and you’re fine.
However the IO objects might need to be initialized within a certain context (e.g. some TLS variables need to be set for tokio). And there might be a few different things one need watch out for. E.g. if you stop the background runtime things might get weird - however this e.g. can't happen with async-std since the background runtime will never shut down.
Specifically where tokio commits to working with other runtimes.
Some arbitrary 3rd party wrapper does not make any guarantees about the runtimes it wraps.
This is just more “happens to work at the moment”.
The problem is not the executor of the futures itself.
But the "Reactor" which is needed to do async-io and the "TimerScheduler" which handle thinks like `.delay(..)` or non os-native timeouts.
What you often can do is having a `Reactor` and time sheduler running for all runtimes involved.
This e.g. works very well with async-std due to it's simple design.
But for tokio it's more complex as you need to provide the reactor and some other hints through something comparable to thread local variables (instead of globals).
This is where the `tokio02` compatibility feature comes in which makes sure to provide access to tokios reactor in the async-std future executors.
Another problem is the global `spawn` method. You normally don't want to run two future executors but as long as their is no abstraction layout over spawn you will have to. Furthermore tokio does a lot of fancy things which you likely will never see in a generic API which is another problem.
For example normally if you want to do blocking code you spawn this in a thread pool to not hinder other async code. But in tokio there is a way to "overlap" the pool of blocking and non-blocking code, basically up to n worker threads of the non-blocking thread pool can be marked at blocking at a time. This is one of many thinks which increase complexity which other runtimes like async std avoided due to it not being worth the additional complexity in >99% of the cases.
Except that tokio is to some degree written for that 1% of cases where all that additional complexity is needed. Because a lot of dev time come from people which work for a company which does need it for their product.
And again, they don’t make GTK and Qt and boost Asia eventloops interoperable. Even if you implement co_await support for each of them individually
The problems all come from async-io.
"Pure" async/await can be mixed and matched in rust without any problems.
But the moment you touch IO (including timeout) a reactor is needed and things get complicated.
Then a lot of objects which require a runtime (e.g. an async socket) might still work on a foreign runtime as long as the original runtime is running. E.g. I think you can use async-std sockets from inside tokio, as long as the async-std runtime is also running in the background. Same is true for the other direction to a certain extent, but it's a bit more complicated to set up.
So overall you will need to understand how the runtimes work and if particular pieces can be mixed and matched. If you don't want to invest in that knowledge => stay on one side.
And before someone starts to blame the Rust ecosystem for that: It's not really different in any other environment. You can't easily mix and match GTK, QT, libuv and boost asio eventloops. It's event a lot harder than trying to combine Rusts async types.