downcast-rs
Safe-Rust trait object downcasting for Box, Rc, and Arc with support for generic and associated types.
Repository Health
Technical Analysis
downcast-rs adds safe downcasting to Rust trait objects, letting you cast a Box<dyn Trait>, Rc<dyn Trait>, or Arc<dyn Trait> back to its original concrete type without touching unsafe. It works by extending std::any::Any’s downcasting machinery to arbitrary traits — including generic traits with type parameters, associated types, and where-clause constraints — through the Downcast, DowncastSend, and DowncastSync traits plus the impl_downcast! macro.
The crate is deliberately small and stable: a single lib.rs, no unsafe code, no_std support via optional std/sync features, and a changelog stretching back to 2016 with mostly incremental refinements. It’s a common building block in plugin systems, ECS-style component stores, and any codebase that stores heterogeneous trait objects in a container and occasionally needs the concrete type back.
What You Get
- Downcast trait and impl_downcast! macro — extend any trait with
is,downcast,downcast_ref, anddowncast_mutmethods generated at the call site. - Box, Rc, and Arc support — downcast owned trait objects behind
Box<dyn Trait>andRc<dyn Trait>, plusArc<dyn Trait>via thesyncfeature andDowncastSync. - Generic trait support — handles type parameters, associated types, and where-clause constraints, including fully concrete instantiations via the
concretemacro variant. - no_std compatibility — builds without the standard library when the
stdfeature is disabled, relying only oncoreandalloc. - Zero unsafe code — the crate is annotated
#![deny(unsafe_code)]and implements everything on top ofstd::any::Any.
Common Use Cases
- Plugin/component systems that store heterogeneous trait objects in a
Vec<Box<dyn Plugin>>and need to recover a specific implementation to call type-specific methods. - Entity-component patterns where components are stored as
Box<dyn Component>and systems downcast to concrete component types. - Error-handling hierarchies where a boxed
dyn Error-like trait object needs to be inspected or converted to a concrete error type for specific handling. - Shared, reference-counted trait objects (
Rc/Arc) in event or message-bus systems where a handler needs to check the runtime type of a shared object.
Under The Hood
Architecture
The entire crate lives in a single src/lib.rs (746 lines): a small trait hierarchy — Downcast (blanket-implemented for any T: Any), DowncastSend (blanket-implemented for T: Any + Send), and DowncastSync (blanket-implemented for T: Any + Send + Sync, gated behind the sync feature) — plus a single impl_downcast! macro. The macro is a recursive tt-muncher that pattern-matches on the caller’s syntax (plain trait, <T> type parameters, assoc associated types, where bounds, and concrete fixed instantiations) and expands to an impl block adding is/downcast/downcast_ref/downcast_mut/downcast_rc/downcast_arc inherent methods directly on dyn Trait. There are no runtime data structures or state — the entire mechanism is compile-time macro expansion over std::any::Any’s existing downcast support, so changing the core abstraction means changing macro-expansion rules, not application logic.
Tech Stack
Pure Rust with zero external dependencies. The crate re-exports std/core (aliased as __std) and alloc (as __alloc) internally so the macro-generated code works identically whether the std feature is enabled or the crate is built no_std. Minimum supported Rust version is 1.56 (edition 2021). CI (GitHub Actions) builds and tests both the stable and 1.56 toolchains, in both default (std+sync) and --no-default-features (no_std) configurations, and runs clippy and cargo doc on each.
Code Quality
Tests live inline in lib.rs behind #[cfg(all(test, feature = "sync"))] and use a second layer of macros (test_mod!, subst_downcast_send!) to generate ten test modules — non-generic, generic, constrained-generic, associated-type, constrained-associated, parameter+associated, and their concrete variants — each exercising the non-sync, Send, and Sync code paths against Box, Rc, and Arc. The crate enforces #![deny(unsafe_code, rustdoc::bare_urls)], so no unsafe code exists anywhere, and all downcast failures return Result/Option rather than panicking. The module-level doc comment doubles as runnable doctests, giving the public API executable, up-to-date examples.
API Design
The public API mirrors std::any::Any’s own naming (is, downcast, downcast_ref, downcast_mut), so anyone familiar with Any can use it immediately, and the common case is a two-line opt-in (trait Foo: Downcast {} + impl_downcast!(Foo)). The macro’s extended syntax for generics, associated types, and where clauses is more particular to this crate and requires consulting the README/doc examples, but each variant is demonstrated inline. Documentation is thorough for a crate of this size, with runnable examples for every supported trait shape embedded directly in the source.