Downcasting is Rust's method of converting a trait into a concrete type.
It is done using the Any
trait, which allows "dynamic typing of any 'static
type through runtime reflection" (docs).
Here's an example showing a simple case of having a trait object that you want to change back into it's original type:
trait Print {
fn start_printing(&self);
}
struct TrustyPrinter;
impl Print for TrustyPrinter {
fn start_printing(&self) {
println!("Starting!");
}
}
fn main() {
// Create your struct that implements your trait
let t = TrustyPrinter;
// Use the trait to abstract away everything that is not needed
let x: &dyn Print = &t;
// Now there's an edge case that uses the original type..
// How do you change it back?
let printer: &TrustyPrinter = x;
}
In order to make use of it, you need to add a method into your trait so the
use std::any::Any;
trait Print {
fn start_printing(&self);
fn as_any(&self) -> &dyn Any;
}
struct TrustyPrinter;
impl Print for TrustyPrinter {
fn start_printing(&self) {
println!("Starting!");
}
fn as_any(&self) -> &dyn Any {
self
}
}
You can now use the downcast_ref
method to convert the reference to your trait object back into your concrete type.
fn main() {
// Create your struct that implements your trait
let t = TrustyPrinter;
// Use the trait to abstract away everything that is not needed
let x: &dyn Print = &t;
// Now you can use the `downcast_ref` method to convert it back
// into `TrustyPrinter`
let printer: &TrustyPrinter =
x.as_any()
.downcast_ref::<TrustyPrinter>()
.expect("Wasn't a trusty printer!");
printer.start_printing();
}
Voilà! You can now freely and carelessly convert between levels of abstraction.
There's a few crates for making this process easier. Namely mopa and downcast-rs.
Unfortunately, much like using Any
, these crates only enable downcasting of traits that you own,
meaning you couldn't downcast from traits from the standard library.
There's a reason why this is so tedious to do and why there's not many usages of this trait in the standard library - it's almost always a bad idea.
Traits exist to create a boundary between the different levels of abstraction, they're not supposed to be able to see outside their own little world. This goes against the concept of separation of concerns and can make your code difficult to understand.
Alternatives to this approach are discussed in my blog post Don't use boxed trait objects for struct internals.