Rust Async Traits in 2026: What’s Stable, What’s Still Broken, and How to Work Around It
Async functions in traits are stable — mostly
Since Rust 1.75, you can write async fn directly inside a trait definition without pulling in the async-trait crate. That was a long time coming. But “stable” doesn’t mean “works everywhere,” and the place it falls apart fast is dynamic dispatch.
What actually works today
Static dispatch is solid. Generic code like this compiles cleanly on any recent stable toolchain:
trait Fetcher {
async fn fetch(&self, url: &str) -> String;
}
async fn run<F: Fetcher>(f: F) {
let result = f.fetch("https://example.com").await;
println!("{result}");
}
No proc macros, no extra dependencies. Just works. Async closures also stabilized in Rust 1.85, giving you async |x: u32| x * 2 as a first-class construct. These implement the new AsyncFn, AsyncFnMut, and AsyncFnOnce traits — mirroring the regular closure hierarchy.
Where it still breaks: dyn Trait
The moment you reach for Box<dyn Fetcher>, the compiler stops you. Async traits are not object-safe. The technical reason is that the compiler can’t know the size of the returned future without knowing the concrete type — and trait objects erase that concrete type.
This isn’t an oversight. Object safety with async traits is genuinely hard to solve without either mandatory heap allocation or some form of type erasure, and the language designers don’t want to silently bake either in. The problem is known; a clean solution isn’t stable yet.
The Send bounds trap
Even with static dispatch, spawning tasks can surprise you. tokio::spawn requires its future to be Send, and Rust can’t always infer that an async trait method’s future satisfies that bound — especially for traits defined in libraries you don’t control.
The trait_variant crate (now maintained under the Rust project umbrella) handles this by generating a Send-bounded companion trait from a single annotation:
#[trait_variant::make(FetcherSend: Send)]
trait Fetcher {
async fn fetch(&self, url: &str) -> String;
}
This produces Fetcher for single-threaded contexts and FetcherSend for use with a multi-threaded executor. Two traits instead of one is verbose, but it’s explicit and zero-cost.
Three options when you need dyn dispatch
Pick based on how much boilerplate you can tolerate:
- The async-trait crate — a proc macro that rewrites async methods to return
Pin<Box<dyn Future>>automatically. Costs one heap allocation per call. Still the easiest drop-in for application code. - Manual Box::pin — declare your trait methods to return
Pin<Box<dyn Future<Output = T> + Send>>yourself. More control, more noise, same allocation cost. - Enum dispatch — if your set of concrete types is fixed and known at compile time, an enum wrapping each variant sidesteps trait objects entirely and stays zero-cost. More refactoring upfront, but no runtime overhead and no workarounds.
Rust 1.95 and what else landed recently
Rust 1.95 (April 2026) didn’t touch the async trait situation directly, but it shipped a couple of things worth knowing about. The new cfg_select! macro gives you compile-time conditional branching with cleaner syntax than the popular cfg-if crate. And if-let guards in match expressions are now stable, which cuts down boilerplate in pattern-heavy code.
Neither is async-specific, but both reduce noise in the kind of platform-conditional, runtime-conditional code that async systems often accumulate.
Where the async working group is headed
The return-position impl Trait in traits feature (RPITIT) laid some of the groundwork for eventual dyn compatibility, and the async working group has been iterating on making object-safe async traits possible without a mandatory allocation. Nothing is stable for that case yet. The Are We Async Yet tracker is the best single place to follow what’s landed and what’s still in progress.
For now the practical rule is simple: native async traits for static dispatch, trait_variant when you need Send, and async-trait or enum dispatch when you need dynamic dispatch.
Sources
- blog.rust-lang.org
- blog.rust-lang.org
- rust-lang.github.io
- microsoft.github.io
- google.github.io
- areweasyncyet.rs
- rust-lang.github.io
- docs.rs
