48

I have a trait that is implemented by some structs. I want to write a pattern match where I can handle every possible case:

trait Base {}

struct Foo {
    x: u32,
}
struct Bar {
    y: u32,
}

impl Base for Foo {}
impl Base for Bar {}

fn test(v: bool) -> Box<Base + 'static> {
    if v {
        Box::new(Foo { x: 5 })
    } else {
        Box::new(Bar { y: 10 })
    }
}

fn main() {
    let f: Box<Base> = test(true);

    match *f {
        Foo { x } => println!("it was Foo: {}!", x),
        Bar { y } => println!("it was Bar: {}!", y),
    }
}

(Playground)

I'm getting this compilation error:

error[E0308]: mismatched types
  --> src/main.rs:25:9
   |
25 |         Foo { x } => println!("it was Foo: {}!", x),
   |         ^^^^^^^^^ expected trait Base, found struct `Foo`
   |
   = note: expected type `dyn Base`
              found type `Foo`

error[E0308]: mismatched types
  --> src/main.rs:26:9
   |
26 |         Bar { y } => println!("it was Bar: {}!", y),
   |         ^^^^^^^^^ expected trait Base, found struct `Bar`
   |
   = note: expected type `dyn Base`
              found type `Bar`
hellow
  • 12,430
  • 7
  • 56
  • 79
Kai Sellgren
  • 27,954
  • 10
  • 75
  • 87

3 Answers3

50

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>).

hellow
  • 12,430
  • 7
  • 56
  • 79
Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • Can I use the named-struct fields with enums? My real structs contain many fields with names and several methods. – Kai Sellgren Sep 30 '14 at 19:47
  • You can put arbitrary data into enum variants. This would work, for example: `struct Foo { f: uint }; enum FooBar { EFoo(Foo) }`. Enums also support fields in their variants (called struct variants): `enum FooBar { Foo { f: uint } }`, though this feature is gated and this is just a syntactic convenience - struct variant is not a struct and can't have methods, for example. – Vladimir Matveev Sep 30 '14 at 19:49
  • Well, Rust *has* the ability to check types at runtime - it is exposed via `Any` trait. `AnyRefExt` has `is::()` method which is essentially the same thing as `instanceof`. However, Rust discourages it in favor of static type checks. It is much safer to write a enum-based `match` dispatch as it is guaranteed to handle every possible case and can be statically checked. – Vladimir Matveev Oct 01 '14 at 14:20
  • I have the hierarchy of traits (BaseTrait -> [ChildTrait1, ChildTrait2,... ChildTraitN]). The implementation structs for the child traits are not exposed, I can operate via traits only. I'm getting an arbitrary instance of BaseTrait, and need to get its exact ChildTrait# to cast against it and perform the ChildTrait#'s specific actions? Will the enum match work for my case? – snuk182 May 23 '16 at 08:16
  • 1
    @snuk182, if you're "getting an arbitrary instance of BaseTrait`, and you cannot change that, then obviously enums won't work for you. Frankly, I don't know what can be done in such case - Rust simply does not have capabilities for such downcasting. If you *are* able to change that, you can use enums with trait objects stored inside enum variants (or with actual implementing types, if you can change that too). – Vladimir Matveev May 23 '16 at 10:50
  • Eventually I did what I needed. https://stackoverflow.com/questions/37654857/rust-trait-objects-self-cannot-be-used-in-a-trait-default-function It required some extra code but now I'm able to cast a general trait to more specific traits. Cast to implementation can still be done via downcast_ref. – snuk182 Jun 06 '16 at 12:22
  • Yes, this is exactly what @VladimirMatveev proposed, and it works well enough. – snuk182 Jun 06 '16 at 12:36
  • This compiles at the time of this writing: https://play.rust-lang.org/?version=stable&mode=debug&edition=2015&gist=19c3e3dec1ba2a142ef6fd8c4571be8a – Richard Gomes Apr 11 '19 at 18:20
8

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 },
});
DenisKolodin
  • 13,501
  • 3
  • 62
  • 65
  • 20
    It's considered good etiquette to [disclose when you recommend something that you have created yourself](http://meta.stackexchange.com/q/15787/281829) – Shepmaster Sep 23 '16 at 13:29
5

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{} ));
}
godsend
  • 51
  • 1
  • 4