Question
Given a trait object stored in Box<dyn A>, how can you recover a Box<B>, &B, or &Box<B> when the underlying concrete value is B?
trait A {}
struct B;
impl A for B {}
fn main() {
let a: Box<dyn A> = Box::new(B);
let b = a as Box<B>;
}
The cast produces error E0605 because as cannot cast a trait object such as Box<dyn A> into a concrete boxed type. What is the correct way to perform this conversion safely?
Short Answer
A Box<dyn A> hides the concrete type that was placed inside it. Rust does not allow an as cast back to Box<B>. When runtime type recovery is genuinely needed, make the trait support std::any::Any and use checked downcasting. You can downcast by reference to obtain &B, or consume the box to attempt to obtain Box<B>.
Concept
A trait object such as dyn A uses dynamic dispatch. It stores a value of some concrete type, such as B, but exposes only the operations promised by trait A.
let a: Box<dyn A> = Box::new(B);
After this assignment, the compiler knows that a owns a value implementing A, but code using a must not assume that the value is specifically B. It could instead have been another type:
struct C;
impl A for C {}
Therefore this conversion is unsafe in principle and Rust rejects it:
// Invalid: `a` might contain a `C`, not a `B`.
let b = a as Box<B>;
The as operator is for a limited set of conversions, mostly numeric casts and pointer-related casts. It does not inspect a trait object's runtime type.
When an application needs to ask, “is this object a B?”, Rust provides std::any::Any. Any supports runtime type identification for types with a 'static lifetime. A downcast is checked: it returns Some or Ok only when the stored value really has the requested type.
In well-designed Rust code, prefer trait methods over downcasting where possible. Downcasting is useful when a heterogeneous collection, plugin system, event system, or framework intentionally stores several concrete types behind one trait.
Mental Model
Think of Box<dyn A> as a sealed package with an interface label saying: “the item inside can perform the actions required by A.”
The package may contain a B, a C, or any other type implementing A. You cannot relabel the package as Box<B> merely because you hope it contains a B.
Any adds a type label inside the package. Downcasting checks that label first:
downcast_ref::<B>()asks to look inside without opening the package.downcast::<B>()opens and consumes the package, returning it asBox<B>only if it really containsB.
This check is why downcasting returns Option or Result instead of directly returning B.
Syntax and Examples
Add Any support to the trait and expose conversion methods that work through the trait object.
use std::any::Any;
trait A: Any {
fn as_any(&self) -> &dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
}
struct B;
impl A for B {
fn as_any(&self) -> &dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
Borrow as &B
Use downcast_ref when you only need to inspect or use the value without taking ownership.
let a: Box<dyn A> = Box::new(B);
if let Some(b) = a.as_any().downcast_ref::<B>() {
// `b` has type `&B`.
println!("The object is a B");
}
Recover ownership as Box<B>
Use downcast when you need to consume Box<dyn A> and recover the boxed concrete value.
let a: Box<dyn A> = Box::new(B);
let b: Box<B> = a
.into_any()
.downcast::<B>()
.expect("expected the trait object to contain B");
downcast::<B>() returns Result<Box<B>, Box<dyn Any>>:
Ok(Box<B>)when the object containsB.Err(Box<dyn Any>)when it contains another type. The original box is returned so it is not lost.
You usually do not need &Box<B>. A Box<B> automatically dereferences to B, so &Box<B> can normally be passed or converted as &B.
Step by Step Execution
Consider a type that carries data:
use std::any::Any;
trait A: Any {
fn as_any(&self) -> &dyn Any;
}
struct B {
id: u32,
}
impl A for B {
fn as_any(&self) -> &dyn Any {
self
}
}
fn main() {
let a: Box<dyn A> = Box::new(B { id: 42 });
match a.as_any().downcast_ref::<B>() {
Some(b) => println!("B id: {}", b.id),
None => println!("The object was not B"),
}
}
Execution trace:
B { id: 42 }is created on the heap byBox::new.- It is stored as
Box<dyn A>, so the public type ofabecomes a trait object. a.as_any()callsB's implementation and returns a shared&dyn Anyreference to the same value.downcast_ref::<B>()compares the runtime type identifier withB.- The types match, so it returns
Some(&B). - The
matchbinds that reference asband prints42. astill owns the boxed value because this was a borrowed downcast, not an ownership transfer.
Real World Use Cases
Downcasting is appropriate when runtime type differences are intentional:
- Event dispatchers: Store several event types as trait objects, then handle a particular event type specially.
- Plugin registries: Keep plugins behind one shared interface while optionally accessing a concrete plugin's extra capabilities.
- GUI or game engines: A collection may store different widgets or components behind a common trait.
- Type-erased application context: A framework can store values of different types and retrieve a known type later.
- Testing and diagnostics: Verify that a factory created a particular concrete implementation.
For normal application behavior, a trait method is often cleaner:
trait A {
fn kind(&self) -> &'static str;
}
If callers only need a behavior or classification, expose it through the trait instead of requiring callers to know every concrete type.
Real Codebase Usage
In production code, use downcasting deliberately and keep it at boundaries where type erasure is introduced.
Prefer behavior-oriented trait methods
Instead of downcasting just to call a B-specific operation, consider making the operation part of the trait:
trait A {
fn process(&self) -> String;
}
Every implementation supplies its own behavior, avoiding type checks in callers.
Use a guard-style type check
When a concrete type is optional, handle failure instead of assuming success:
fn print_b_id(value: &dyn A) {
let Some(b) = value.as_any().downcast_ref::<B>() else {
return;
};
println!("Found B with id {}", b.id);
}
Handle ownership failures
If ownership is required, preserve or report the nonmatching value:
fn take_b(value: Box<dyn A>) -> Result<Box<B>, Box<dyn Any>> {
value.into_any().downcast::<B>()
}
Consider enums for a fixed set of variants
If your program has a small, known list of types, an enum makes alternatives explicit and is often simpler than trait objects plus downcasting.
Common Mistakes
Using as for trait-object downcasting
// Does not compile.
let b = a as Box<B>;
as does not validate a dynamic type and cannot convert Box<dyn A> into Box<B>. Use Any and downcast instead.
Assuming every A is a B
struct C;
impl A for C { /* ... */ }
let a: Box<dyn A> = Box::new(C);
let b = a.into_any().downcast::<B>(); // Err(...)
A trait object can hold any implementing type. Always handle Option or Result, unless failure truly indicates a programmer bug and expect has a useful message.
Calling downcast_ref directly on dyn A
// Does not compile if `A` does not expose `Any`.
// let b = a.downcast_ref::<B>();
downcast_ref is a method of dyn Any, not arbitrary traits. Make the trait extend Any and provide as_any.
Requesting &Box<B> unnecessarily
let boxed: Box<B> = Box::new(B);
let borrowed: &B = &boxed;
Most functions should accept &B, not &Box<B>. Box is an ownership container; borrowing through it gives direct access to B.
Downcasting when polymorphism is enough
If each concrete type should perform the same operation differently, add that operation to the trait. Repeated downcasts and long chains of if let checks often indicate that the trait API is missing a needed method.
Comparisons
| Approach | Best when | Result on type mismatch | Ownership |
|---|---|---|---|
| Trait method / dynamic dispatch | Callers need common behavior | Not applicable | Depends on method receiver |
Any::downcast_ref::<T>() | Inspect or use a known concrete type | None | Keeps original owner |
Any::downcast_mut::<T>() | Modify a known concrete type | None | Keeps original owner |
Box<dyn Any>::downcast::<T>() | Recover a boxed concrete value | Err(original_box) | Consumes the box |
Enum with match | All variants are known and finite | Compiler checks every variant | Depends on matching style |
downcast_ref and downcast_mut borrow the object. downcast transfers ownership and is the appropriate choice when the caller needs Box<B>.
An enum is usually preferable to downcasting when the set of possible types is fixed by your application. Trait objects are more useful when implementations are extensible or supplied independently.
Cheat Sheet
use std::any::Any;
trait A: Any {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
}
| Need | Operation | Return type |
|---|---|---|
Read B without consuming | value.as_any().downcast_ref::<B>() | Option<&B> |
Mutate B without consuming | value.as_any_mut().downcast_mut::<B>() | Option<&mut B> |
Take ownership of B | value.into_any().downcast::<B>() | Result<Box<B>, Box<dyn Any>> |
Rules:
as Box<B>is not valid for converting a trait object to its concrete type.- The trait must support
Any;Anyrequires concrete stored types to be'static. - A failed downcast is normal when several concrete types can be stored.
- Prefer
&Bto&Box<B>in function parameters. - Prefer trait methods or an enum if callers should not depend on concrete types.
FAQ
Can I cast Box<dyn A> to Box<B> with as in Rust?
No. Rust does not permit that cast because a Box<dyn A> might hold any type implementing A. Use checked Any downcasting when runtime type recovery is needed.
How do I get &B from &dyn A?
Make A extend Any, expose fn as_any(&self) -> &dyn Any, then call value.as_any().downcast_ref::<B>(). It returns Option<&B>.
How do I get Box<B> from Box<dyn A>?
Consume the trait-object box through an into_any method, then call downcast::<B>(). The result is Result<Box<B>, Box<dyn Any>>.
Why does downcasting return Option or Result?
The requested type may not match the concrete value stored in the trait object. Rust requires you to handle that possibility safely.
Is &Box<B> different from &B?
Yes, but &Box<B> is rarely useful. Because Box<B> dereferences to B, APIs generally accept &B when they only need borrowed access to the value.
Should I use downcasting often in Rust?
Usually no. Prefer putting shared operations in the trait. Downcasting is best reserved for intentional runtime type inspection, such as plugin or event systems.
Can all trait objects be downcast?
No. The trait must be designed to support runtime type identification, typically by extending std::any::Any and exposing an Any view.
Mini Project
Description
Build a small message processor that stores different message types behind one trait object. The program will use a shared trait method for normal processing and Any downcasting only when it needs data specific to one message type.
Goal
Store Email and Sms values as Box<dyn Message>, process them polymorphically, and safely identify email messages at runtime.
Requirements
- Define a
Messagetrait that supportsAnydowncasting. - Create
EmailandSmsstructs that implementMessage. - Store both message types in a single
Vec<Box<dyn Message>>. - Print a common summary for every message.
- Downcast only email messages and print their subject.
Keep learning
Related questions
Absolute Values in Rust: Using the abs() Method
Learn how to calculate absolute values in modern Rust with abs(), handle signed integer overflow, and choose safer alternatives.
Accepting Iterators in Rust Functions with impl Iterator
Learn how to write Rust functions that accept HashMap iterators using generic bounds or impl Iterator, avoiding trait object and lifetime errors.
Access Enum Variant Values in Rust with Pattern Matching
Learn how to extract and print values stored in Rust enum variants using match and if let pattern matching, with practical examples.