Async Rust never left the MVP state
rustasync rusttokioasync-stdprogrammingsoftware developmentconcurrencyruntimethreadingdeveloper experiencebackend developmentecosystem

Async Rust never left the MVP state

Tokio's Monoculture and the Send + 'static Trap

Here's the thing: Tokio became the "One True Runtime." It's not just a runtime; it's a whole framework, pulling in fs, io, net, process, and signal handling. It pioneered a lot of async Rust design, and for that, credit is due. But its default configuration, the features = ["full"] that everyone reaches for, sets up a multi-threaded, work-stealing runtime.

This design, I'd argue, was a premature optimization. It forces types to be Send and 'static'. What does that mean for you? It means using references in async code becomes a nightmare. The elegant borrowing model that makes synchronous Rust so powerful? Gone. You're suddenly reaching for Arc and Mutex just to pass data around, introducing runtime overhead, memory usage, and build time hits. This isn't zero-cost abstraction; it's a silent debugging burden, a tax on your mental model. It's the "Original Sin" that makes async Rust feel so alien compared to its synchronous sibling.

A tangled knot of glowing red and blue wires, representing complex data dependencies and synchronization issues in a server rack, with a blurred background of blinking server lights.
Tangled knot of glowing red and blue wires

The Runtime Graveyard and the Threading Alternative

The ecosystem fragmentation doesn't help. async-std tried to offer an alternative, closer to the standard library. It was a noble effort, but as of March 1, 2025, it's officially discontinued. Abandoned. Now, if you picked that, you're looking at migrating to smol or something else. This kind of churn is exactly why developers get wary.

You've got smol for lightweight stuff, embassy for embedded, glommio for io_uring heavy workloads. That's good for specialization, but it means library authors have to support multiple runtimes or force users into a specific one, leading to "executor coupling" and ecosystem silos.

And let's be blunt: for many conventional server-side applications on Linux or Windows, threads are often simpler and just as performant. Modern operating systems have highly optimized schedulers. io_uring and splice handle async I/O efficiently at the kernel level. Benchmarks show traditional threading can even outperform async in scenarios with a limited number of threads. I've seen thread-based frameworks like iron handle tens of thousands of requests per second without breaking a sweat. Rust's threading model is safe, preventing data races and null dereferences.

The "coloring problem"—the difficulty of mixing async and sync I/O—is a constant headache. You end up with block_on calls that can deadlock or starve your executor, or you infect your entire codebase with async keywords just to call one I/O function.

The Two-Tiered Ecosystem We Didn't Ask For

This pursuit of "zero-cost abstractions" in async Rust has, ironically, created a higher cost for developers. It's built a two-tiered ecosystem where synchronous Rust is simple, predictable, and ergonomic, and asynchronous Rust is brittle, complex, and requires an exceptionally high level of low-level knowledge. Debugging runtime issues feels like archaeology. There's no solid structured concurrency, and async Drop is still a missing piece.

Library maintainers bear the brunt, supporting both async and sync interfaces, or worse, forcing async-only APIs that limit downstream integration. For embedded systems, where every byte and cycle counts, the bloat and less-than-zero-cost abstractions from the current async model are a real problem.

A developer hunched over a glowing laptop in a dimly lit server room, surrounded by complex code on multiple screens, looking frustrated and tired.
Developer hunched over a glowing laptop in

Pick Your Battles, Wisely

Here's my take: Async Rust is a specialized tool. It's not the default. It's not a magic wand.

  1. Learn synchronous Rust first. Master it. It's the foundation.
  2. Use Async Rust sparingly. Only when you have a clear, undeniable need for extreme concurrency or specific embedded constraints where threads aren't an option.
  3. If you go async, stick to Tokio. It's the monoculture, yes, but it has the most established libraries (reqwest, sqlx) and the largest community. You'll find some answers, even if they're painful.
  4. Isolate your async code. Keep your core domain logic synchronous. Use async only for the I/O boundaries and external service calls.
  5. Avoid async-only interfaces in public libraries. Don't force your users into this mess.

The default mode for writing Rust should be synchronous. The complexity and maintenance overhead of async Rust, coupled with its current ergonomic rough edges and ecosystem fragmentation, mean its benefits are often outweighed by its costs for common backend tasks. We need a fundamental shift away from runtime coupling and towards a more composable, ergonomic async story. Until then, treat it like the powerful, but still-in-progress, MVP it is.

Alex Chen
Alex Chen
A battle-hardened engineer who prioritizes stability over features. Writes detailed, code-heavy deep dives.