Can I do type introspection with trait objects and then downcast it?
Solution 1:
As you have noticed, downcasting only works with Any
trait, and yes, it only supports 'static
data. You can find a recent discussion on why it is so here. Basically, implementing reflection for references of arbitrary lifetimes is difficult.
It is also impossible (as of now, at least) to combine Any
with your custom trait easily. However, a macro library for automatic implementation of Any
for your trait has recently been created. You can also find some discussion on it here.
Solution 2:
This isn't a Rust-specific problem, although the vocabulary may be a little different. The ideal way to solve a problem like this, not just with traits in Rust but in any language, is to add the desired behavior (foo_method
in your example) to the abstract interface (Trait
):
trait Trait {
fn trait_method(&self);
fn foo_method(&self) {} // does nothing by default
}
struct Foo;
impl Trait for Foo {
fn trait_method(&self) {
println!("In trait_method of Foo");
}
fn foo_method(&self) {
// override default behavior
println!("In foo_method");
}
}
struct Bar;
impl Trait for Bar {
fn trait_method(&self) {
println!("In trait_method of Bar");
}
}
fn main() {
let vec: Vec<Box<dyn Trait>> = vec![Box::new(Foo), Box::new(Bar)];
for e in &vec {
e.trait_method();
e.foo_method();
}
}
In this example, I have put a default implementation of foo_method
in Trait
which does nothing, so that you don't have to define it in every impl
but only the one(s) where it applies. You should really attempt to make the above work before you resort to downcasting to a concrete type, which has serious drawbacks that all but erase the advantages of having trait objects in the first place.
That said, there are cases where downcasting may be necessary, and Rust does support it -- although the interface is a little clunky. You can downcast &Trait
to &Foo
by adding an intermediate upcast to &Any
:
use std::any::Any;
trait Trait {
fn as_any(&self) -> &dyn Any;
}
struct Foo;
impl Trait for Foo {
fn as_any(&self) -> &dyn Any {
self
}
}
fn downcast<T: Trait + 'static>(this: &dyn Trait) -> Option<&T> {
this.as_any().downcast_ref()
}
as_any
has to be a method in Trait
because it needs access to the concrete type. Now you can attempt to call Foo
methods on a Trait
trait object like this (complete playground example):
if let Some(r) = downcast::<Foo>(&**e) {
r.foo_method();
}
To make this work, you have to specify what type you expect (::<Foo>
) and use if let
to handle what happens when the referenced object is not an instance of Foo
. You can't downcast a trait object to a concrete type unless you know exactly what concrete type it is.
If you ever need to know the concrete type, trait objects are almost useless anyway! You probably should use an enum
instead, so that you will get compile-time errors if you omit to handle a variant somewhere. Furthermore, you can't use Any
with non-'static
structs, so if any Foo
might need to contain a reference, this design is a dead end. The best solution, if you can do it, is to add foo_method
to the trait itself.