async/.await in Rust is a perfect example of "code duplication" in the language core. Okay, you have a nice syntax for performing do-notation on futures, but what about iterators, streams, etc? We have generators in nightly and some third-party macros for streams, which sucks.
A proper algebraic effect system could resolve the problem. You can take a look at Koka to see how elegantly it abstracts common control flow patterns using the concept of an algebraic effect.
Algebraic effects are still a research area - no mainstream language has them fully working - but basic Haskell-style higher-kinded types should not be so much of a stretch, and are a necessity if you want reusable libraries for this kind of stuff.
A proper algebraic effect system could resolve the problem.
It could also take as long as the async MVP itself to get off the ground, and you can't know if it wouldn't hit its own snags when deployed at scale. (From long ago, I remember the "non-parametric dropck" RFC as an illustration of a neat concept colliding with reality.)
Languages with mainstream aspirations evolve under greater pressure. I don't know this, but I strongly suspect that async was necessary for Rust to gain acceptance at, say, Amazon. So it couldn't practically have been designed with a wide-open timeframe. The result is what we have; one can uncharitably call it "half-baked" and tut-tut about the sharp edges, but it's workable if you know what to avoid. (But it's so tempting to imagine what could have been, eh?)
A proper algebraic effect system could resolve the problem. You can take a look at Koka to see how elegantly it abstracts common control flow patterns using the concept of an algebraic effect.