Well that isn't entirely true. Java is doing it in a similar way to Go in that every call to I/O or other function that would block can suspend the current virtual thread. But if it's called in a standard blocking thread then it blocks as expected (in Java w/Loom that is, ofc every goroutine is a green thread). This is of course a ton easier in Java and Go because of the managed memory model.
However I would be interested in hearing what the specific Rust features (or lack thereof) that prevent this being possible. If it's possible to swap the async runtime out it seems like it should also be possible to add enough runtime to a Rust program to allow the equivalent of virtual threads as long as stuff uses stdlib sockets, file I/O etc.