async-trait
Type erasure for async trait methods
Async-trait explained
Rust is a systems programming language that has excellent support for writing fast, concurrent code. One common pattern is to define a trait—essentially a contract that says "any type implementing this trait must have these methods"—and then use that trait as a flexible interface. For years, however, there was a frustrating limitation: you couldn't write async methods (methods that can pause and resume work) inside a trait and then use that trait as a dynamic type that could hold different implementations at runtime.
This library solves that problem with a single macro you add above your trait definition. Once you mark your trait with #[async_trait], you can write async methods inside it and treat it like any other trait. The README example shows an advertising platform where different ad types (like modals and autoplay videos) implement a common trait with an async run() method. Without this library, that design wouldn't work; with it, you just add the macro and everything works as you'd expect.
Behind the scenes, the macro does some mechanical translation. It converts async methods into regular methods that return a special "future" object—think of it as a paused computation that can be resumed later. This is how Rust actually implements async under the hood, and the macro just handles the plumbing so you don't have to write it by hand. The key insight is that this makes the trait compatible with dynamic dispatch, the feature that lets you store different concrete types behind a shared interface.
The library handles most Rust trait features you'd expect: methods with or without self, generic types, associated types, default implementations, and more. There are two main caveats: first, if your async code needs to work across thread boundaries, the macro adds some extra bounds automatically (though you can opt out with #[async_trait(?Send)]). Second, lifetimes (annotations about how long borrowed data lives) must be written explicitly in async functions—but the compiler gives clear error messages when you forget.