|
It's really not as bad as it's made out to be. You can paint yourself into a corner with it, but a lot of that is that async is fundamentally more complicated than sync / threaded code, and there's only so much any language can do to paper that over. Rust exposes a lot of details, so it can be complicated to get to grips with how they combine with async in certain corner cases, but the happy path is quite happy even now. A lot of the async Rust code I work with already looks like `async fn foo() -> ... { do_request().await?.blah().await }`, plus the occasional gathering of futures into a `Vec` to join on. That sort of thing, not much different from Javascript, but with a lot more control of the low-level details. A good deal of corner cases should get better once async traits are stabilized, which will mean much less need for manually writing out Future types. But honestly, even now it's not that bad. I have a codebase that uses async to read hundreds of thousands of files[1], streaming gunzip them, pass them to another future which streaming parses records from them, and then pushes those parsed records into a `FnMut` closure for further non-async processing. It took a bit of thinking and design to get everything moving together nicely, but that corner of the codebase now is only ~200 lines of pretty straightforward code -- there's like 1 instance of `Unpin`. It's not that bad. [1]: I know async isn't necessarily faster for reading files, but it started life doing network requests and it can still saturate a 200-core machine so I haven't felt the need to port it over to threads. |
The most egregious code comes when implementing one of the `AsyncRead`/`AsyncWrite` traits or similar, and that can come up a bunch in backend services, for example if you want to record metrics on how/when/where data flows, apply some limits etc. I'm curious how the ecosystem will adapt once async trait methods land for real.