Comment by SkiFire13
4 hours ago
> flow that crosses several state transitions (and may have other disjoint parts of the state updated in the meantime, via other transitions) now has to be rewritten as a (series of) state transitions, and can't be called from a blocking function
This is incorrect. You can easily call that flow from a blocking function, just call it in a loop.
What normally prevents a blocking function from "simply" calling an async one is that the async function needs runtime support for certain operations: the simpliest of which is suspending until it's notified, with more complex ones being async I/O primitives.
A Sans-IO library needs none of these. It can simply be called in a loop and as long as the caller provides more inputs it will make progress. An blocking caller can simply call the std's `Read::read` and pass the received bytes in, while an async caller can use async I/O primitives to do the same, and since the Sans-IO library yields once it finishes consuming the current input it won't block the async library either.
It's the best of both world but as always you have a tradeoff, and in this case it's the ability to express the state machine as if it was a straightforward function. Maybe once Rust gets support for generic coroutines this will get better.
> This is incorrect. You can easily call that flow from a blocking function, just call it in a loop.
Sure, the same can be said of an async library — so long as you're okay with none of your other flows being able to make progress in the meantime. You can just poll in a loop.
> It's the best of both world but as always you have a tradeoff, and in this case it's the ability to express the state machine as if it was a straightforward function.
‘The best of both worlds’ means that it shares the advantages of both, but the main (arguably only) advantage of async/await is that you get to write your temporally separated flows sequentially. (And maybe also that they look a bit like functions, I guess.) That's not true here.
> A Sans-IO library needs none of these.
To be clear I'm not by any means arguing that ‘sans-IO’ (or, as we used to call it, pure functional programming) is a bad thing. I'm very in favour of it. I'm just saying that a ‘sans-IO’ design shouldn't need to eschew async/await, which you can write in terms of pure functions.
(As a sibling comment points out, there are languages where that is not true, i.e. async/await inherently introduces some kind of side effect around concurrency or a global event loop. Thankfully that's not the case in Rust.)