Strategy using traits

I am having trouble finding the right approach to my following problem.

I have two structs Foo and Bar which both implement MyTrait but only Foo also implements MyOtherTrait

A struct Storage stores Foo and Bar.in the field data which takes Vec<Box<dyn MyTrait>>

When I access data I can use all methods of MyTrait given my definition of Storage

How do I access all functions of Foo in this case? What is a good approach here?

trait MyTrait {
    fn give_number(&self,i: i32) -> i32; 
}

trait MyOtherTrait {
    fn hello(&self) -> String;
}

#[derive(Clone)]
struct Foo{}
#[derive(Clone)]
struct Bar{}

impl Foo {}

impl MyTrait for Foo {
    fn give_number(&self,i: i32) -> i32 {
        i
    }
}


impl MyOtherTrait for Foo {
    fn hello(&self) -> String {
        "Hello_Foo".to_string()
    }
}




impl MyTrait for Bar {
        fn give_number(&self,i: i32) -> i32 {
        i
    }
}

impl Bar {}


struct Storage {
    data: Vec<Box<dyn MyTrait>>,
 }

fn main() {
    
    let s = Storage{data: vec![Box::new(Foo{}), Box::new(Bar{})]};
    println!("{:?}", s.data[0].hello()) // compile error method not found in `std::boxed::Box<(dyn MyTrait + 'static)>`
}

I am a self taught rather inexperienced programmer and sometimes circle around "strategies" how to approach a problem. But rust really is showing me my limits...

Thanks!

You can downcast the type to Foo using Box::downcast.

What do MyTrait and MyOtherTrait represent? If you have a fixed number of implementors, it will be better to just use an enum instead of a trait.

Thanks for your swift reply. I have thought about this solution and asked a questions previously regarding downcasting.

Where I having trouble with is where to downcast? Would it make sense to implement something like get_foo in MyTrait?

If you're using this enough that you're considering making it part of the trait, I recommend making it an enum instead as suggested by @RustyYato

i.e. you could do something like

enum MyEnum {
    Foo(Foo),
    Bar(Bar),
    // ...
}

Then you can pattern match to get Foo

Thanks for your feedback.

I have also thought about this approach. But I am not 100% sure how to do this reasonable.

Let's say Foo and Bar share some methods. This would mean I have to pattern match Foo and Bar for every function, right?

Or do you mean I should implement something like get_foo using Enums?

Thanks!

If they share methods then you can add those methods to MyEnum

impl MyEnum {
    fn shared_method(&self) {
        match self {
            Self::Foo(foo) => foo.shared_method(),
            Self::Bar(bar) => bar.shared_method(),
        }
    }
}

Or you could even just inline those methods into MyEnum if you want. If you have a lot of these functions, you could use a macro to ease the boilerplate.

2 Likes

Great. I didn't know I could do this.

Really happy with this forum. Very helpful and patient with newbies.

Thanks!

4 Likes

Alternatively, if the implementation is the same, you could keep the trait and use a default implementation: https://doc.rust-lang.org/book/ch10-02-traits.html#default-implementations

Thanks to both of you!

Another approach would be to store Foos in one Vec<Foo> and Bars in Vec<Bar>.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.