I was reading about Rust plans for 2023 (as outlined by Nico) and also about questions raised by ZiCog:
Why do we have synchronous calls to OS services at all? Why do read() and friends hang up the caller for ages rather than return immediately and then indicate completion later, with some kind of signal or event? Or even crudely providing something that can be polled for completion?
How would our programming languages look if we never had synchronous system calls?
How would the code look that those compilers generated?
Without synchronous, blocking, system calls normal threading as we know it, pthreads in C, std::thread in Rust and many other may never have needed to exist. Would all languages then be async by default?
And then I did a tiny experiment: what would happen if we pretend, for a moment, that there are no difference between sync and async
functions?
Yeah, async
function return reference to function frame and make it possible to poll
them to the completion (they are generators deep in heart, not a regular functions), but still, it's obvious how one may convert async
function into sync one in an “extra naïve way”. So we can take something like this:
extern "C" {
// Make sure calculated value goes “somewhere outside” and not optimized away
fn my_c_function(x: *mut i32);
}
pub async fn square_async(num: i32) {
unsafe { my_c_function (&mut (num * num)) }
}
And then make it sync with an extra-naïve, super-dumb executor:
pub fn square_async_to_sync(num: i32) {
let waker = Waker::noop();
let mut cx = Context::from_waker(&waker);
let mut fut = pin!(square_async(num));
loop {
if let Poll::Ready(res) = fut.as_mut().poll(&mut cx) {
return res
}
}
}
And then we compile it, compare to synchronous version… and we get the exact same machine code, of course. Simple dead code elimination, nothing magical.
But then… what's the point of “maybe async
” feature? It seriously looks like a solution in a search of a problem.
What we really need is to solve a completely different task from the same post: “traits for being generic over runtimes”.
If we can do that then we may provide a “naïve, synchronous, runtime”, postulate that attempt to use await
with async
function from normal, synchronous, function injects dummy loop with noop
waker… and voila: done. Everything works. Tiny change to the compiler, tiny change to the language.
And I may not understand some deep implications but I couldn't see how any “maybe async
” feature would be able to magically remove all these pollings and wake up machinery if I wouldn't be able to magically switch my function from poll_read to read — and if I'm not mistaken then “maybe async
” proposals don't include anything like that.