downcast-rs

Safe-Rust trait object downcasting for Box, Rc, and Arc with support for generic and associated types.

Library
Cargo
v2.0.2
220stars
MIT OR Apache-2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
32/100Needs Attention
Development Activity0
Maintenance0
Community48
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
75/100Good
Architecture72
Code Quality85
Innovation78
Learning Curve65

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, and downcast_mut methods generated at the call site.
  • Box, Rc, and Arc support — downcast owned trait objects behind Box<dyn Trait> and Rc<dyn Trait>, plus Arc<dyn Trait> via the sync feature and DowncastSync.
  • Generic trait support — handles type parameters, associated types, and where-clause constraints, including fully concrete instantiations via the concrete macro variant.
  • no_std compatibility — builds without the standard library when the std feature is disabled, relying only on core and alloc.
  • Zero unsafe code — the crate is annotated #![deny(unsafe_code)] and implements everything on top of std::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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search