How to match trait implementors
You can't. Traits do not support downcasting - Rust is not inheritance/subtyping-based language, and it gives you another set of abstractions. Moreover, what you want to do is unsound - traits are open (everyone can implement them for anything), so even if in your case match *f
covers all possible cases, in general the compiler can't know that.
You have two options here. If you know the set of structures implementing your trait in advance, just use enum, it's a perfect tool for this. They allow you to statically match on a closed set of variants:
enum FooBar {
Foo(u32),
Bar(u32),
}
fn test(v: bool) -> FooBar {
if v {
FooBar::Foo(5)
} else {
FooBar::Bar(10)
}
}
fn main() {
let f: FooBar = test(true);
// Now that we have a `Box<Base>` (`*f` makes it a `Base`),
// let's handle different cases:
match f {
FooBar::Foo(x) => println!("it was Foo: {}!", x),
FooBar::Bar(y) => println!("it was Bar: {}!", y),
}
}
(Playground)
This is by far the simplest way and it should always be preferred.
Another way is to use Any
trait. It is a facility for type-safe downcasting from trait objects to regular types:
use std::any::Any;
struct Foo {
x: u32,
}
struct Bar {
y: u32,
}
fn test(v: bool) -> Box<Any + 'static> {
if v {
Box::new(Foo { x: 5 })
} else {
Box::new(Bar { y: 10 })
}
}
fn main() {
let f: Box<Any> = test(true);
match f.downcast_ref::<Foo>() {
Some(&Foo { x }) => println!("it was Foo: {}!", x),
None => match f.downcast_ref::<Bar>() {
Some(&Bar { y }) => println!("it was Bar: {}!", y),
None => unreachable!(),
},
}
// it will be nicer when `if let` lands
// if let Some(ref Foo { x }) = f.downcast_ref::<Foo>() {
// println!("it was Foo: {}!", x);
// } else if let Some(ref Bar { y }) = f.downcast_ref::<Bar>() {
// println!("it was Bar: {}!", y);
// } else { unreachable!() }
}
(Playground)
Ideally it should be possible to write something like this:
trait Base: Any {}
impl Base for Foo {}
impl Base for Bar {}
and then use Base
in the code, but it can't be done now because trait inheritance does not work with trait objects (e.g it is impossible to go from Box<Base>
to Base<Any>
).
I suggest the Visitor Pattern to match on traits. This pattern is from OOP but it shines in many situations. Moreover, it's more efficient while avoiding downcasting.
Here is such a snippet:
struct Foo{ value: u32 }
struct Bar{ value: u32 }
trait Base<T> {
fn accept(&self, v: &dyn Visitor<Result = T>) -> T ;
}
impl <T>Base<T> for Foo {
fn accept(&self, v: &dyn Visitor<Result = T>) -> T {
v.visit_foo(&self)
}
}
impl <T>Base<T> for Bar {
fn accept(&self, v: &dyn Visitor<Result = T>) -> T {
v.visit_bar(&self)
}
}
trait Visitor {
type Result;
fn visit_foo(&self, foo: &Foo) -> Self::Result;
fn visit_bar(&self, bar: &Bar) -> Self::Result;
}
struct StringVisitor {}
impl Visitor for StringVisitor {
type Result = String;
fn visit_foo(&self, foo: &Foo) -> String {
format!("it was Foo: {:}!", foo.value)
}
fn visit_bar(&self, bar: &Bar) -> String {
format!("it was Bar: {:}!", bar.value)
}
}
fn test<T>(v: bool) -> Box<dyn Base<T>> {
if v {
Box::new(Foo{value: 5})
} else {
Box::new(Bar{value: 10})
}
}
fn main() {
let f = test(true);
println!("{:}", f.accept( &StringVisitor{} ));
}
You can use my match_cast
crate:
match_cast!( any {
val as Option<u8> => {
format!("Option<u8> = {:?}", val)
},
val as String => {
format!("String = {:?}", val)
},
val as &'static str => {
format!("&'static str = {:?}", val)
},
});
match_down!( any {
Bar { x } => { x },
Foo { x } => { x },
});