When I started out, it felt icky and like some kind of infection that everything eventually had to be async, but that was because I didn't understand how to interact with async code. Using spawn, spawn_blocking, and futures::stream is 90% of my use case, and it makes it easy to set up "barriers" beyond which async doesn't need to spread.
DIP, async/await, coloured-functions, io-monad. All cases where the business logic should go in one column (abstract/sync/blue/pure), and your machinery should go in the other column (concrete/async/red/io). Your logic will remain unit-testable that way, regardless of how big or small it gets.
Irritated newcomers will complain about not being able to call in the other direction. So what's a language-designer to do? Attract the largest possible user-base by not getting in the user's way - or filter out frustrated users and have fewer but better codebases out there?
The typical pattern I enjoy is:
* Sync for the logic
* Async for the job runners b/c I'd like to do other things while it's going.
* Sync or Async as required for the data wrangling (db calls, network, filesystem)
So you have essentially:
1. kick off job, returning a future, by calling an async function.
2. Job is mostly async data wrangling, followed by a sync logic block that's unit tested, followed by an async "save" if required.
3. Either wait for the future, or run the task in the background, or `.await` if you're in an async block.
I'm hand waving away the syntax, but that's the mental model. 99% of code isn't async, and the code that is isn't async for the fun of it.
Especially when using tokio I feel like my solutions work out pretty well. I also very much appreciate how they create async equivalent functions for pretty much any std function worth using. Maybe this creates a divide, but my concurrent programs feel easy to code and the performance is pretty good.
My only semi-issues are probably stuff like task cancellation, however I attribute that to skill issues on my end, rather than tokio or the way async/await is implemented in Rust.