> 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.