As for the suggestion to wrap things that must not do async into a sync function (or maybe a local closure) ‒ I’ve already thought of that. And I would probably structure my code that way, but it’s an ugly hack at best, because:
- I think it’ll be less rare in Rust than you think. You still seem to consider Rust to be one more Kotlin or Go, designed to just run your server code, but fast… while Rust can do that, what makes it unique is it’s very low-level nature, allowing you to do all kinds of crazy things while preserving at least some sanity. Degrading this support for the sake of a code that pretends not to be async on the surface doesn’t sound correct in that context.
- If the author decides to write it that way, sure it becomes more readable in the code review or when hunting for bugs. But if the author forgets or doesn’t know about the trick, the review is still tedious ‒ in other words, I’d either have to mandate that every place with a mutex held is wrapped into a sync function, or give up on that and still suspect every single thing.
- This doesn’t go well with the „pit of success“ principle. It should be easier to do the right thing (writing a code that is clear about the intentions, here), while here it looks like the right thing is much more convoluted.
You state that now you want you async fn to return future too, but that it runs it right away… how does it fit into any mental model? How does the async fn's call expand into something that does the NotReady loop and pinning? How can the function (if it’s a function) influence what happens on the caller side?
How much is that async fn just fn → Future? If I have another function, like this:
async fn measure_time<R, F: FnOnce() -> R>(f: F) -> R {
let before = ntp_get_time(); // This one is async
let result = f(); // ????
let after = ntp_get_time(); // Async
print("It took: {}", after - before);
}
Now, if I pass a sync function, it does something I expect. How should it act when I pass an async fn? Should it not compile, because async fn isn’t just fn → Future, at least sometimes (inconsistent), should it create and await that future (probably consistent but very surprising to the author of the measure_time or should it just return the Future, because some kind of other exception (inconsistent)?
I think implicit await fits well with languages where the async is really a core feature of the language, like Go, where everything is just async and implicit await in a sense. But I feel Rust is built around different models and async is just an extension, one of many and in much of the code isn’t async. It feels like the IO monad in Haskell to me a bit ‒ you could write all your program in the IO monad, but the usual style is to do it only on the top level ‒ to read/write the input, but the the computation in pure code. In Rust I would imagine the usual pattern would be to do the async somewhere on the top level (maybe just one async fn, with few suspension points) and all the computations to be done in sync code. Sure, not everything fits there.