You get the same thing in every language where you can choose a runtime. Whether the colors are enforced by the compiler is another matter, but since you can only have one runtime per thread and each runtime has a different API you inherently get runtime colors.
This isn't actually a problem most of the time because we can spawn new threads, and so in C++ with co_await I have like three or four different "runtimes" all working at once with their own thread pools and their own I/O loops and I have no issue mixing and matching their behaviors using the obvious syntax. The issue with Rust seems to come down to something much deeper involving either an overzealous attempt to force people to think in terms of "executors" and "runtimes" for entire stacks of coroutines instead of individual tasks or (at best) as a result of decisions made with respect to how the memory management of the mechanism would operate in the context of Rust.
Why can’t a single thread run multiple runtimes’ event loops? It’s pretty limited what an event loop can do: it can wait on a set of FDs, or sleep for some time (or until woken up). So if the various runtimes’ event loops could implement a common interface, then I don’t see why they couldn’t share a single thread…
> It’s pretty limited what an event loop can do: it can wait on a set of FDs, or sleep for some time (or until woken up).
But that's what the runtime is. There's multiple strategies for implementing an event loop (as well as multiple async platform APIs to use), which all directly affect the lower-level abstractions. By replacing the event loop and lower-level abstractions with one common implementation all you've done is add another competing runtime. You can't have more than one event loop and thus you can't have more than one runtime.
If you've got a thread running one event loop, how could you signal to it to temporarily break out of that loop and run a different event loop for a while?
The while(1) part of the event loop would be outside, provided by the stdlib. Each runtime would provide a set of FDs that they want to monitor, and a function to execute a single iteration of their event loop. The while(1) loop would then be calling each runtime in a round-robin fashion, then go sleep while waiting on the union of all the runtimes’ FD sets. It would be sort of an “event loop of event loops”.
What you've just described is ... an executor runtime. Even if you boil it down to doing "only" that, you still can only have one of that per thread, and there isn't just one way to implement that.