How to insert generic struct into a concrete collection?

trait Shine {
    fn shine(&self);
}

struct Red {}
impl Shine for Red {
    fn shine(&self) {
        println!("Reddish shine.")
    }
}

struct Blue {}
impl Shine for Blue {
    fn shine(&self) {
        println!("Blueish shine.")
    }
}

struct Green {}
impl Shine for Green {
    fn shine(&self) {
        println!("Greenish shine.")
    }
}

struct ColoredCarsDealership {
    red_cars: Vec<Car<Red>>,
    blue_cars: Vec<Car<Blue>>,
    green_cars: Vec<Car<Green>>,
}

struct Car<C: Shine> {
    color: C,
}

impl<C: Shine> Car<C> {
    pub fn new(color: C, dealership: &mut ColoredCarsDealership) {
        let new_car = Car { color };
        // insert(new_car, dealership); <-- How to do this
    }
}

Am I thinking the right way?

Link to playground.

You could implement this using trait objects, but it would require refactoring your generics:

struct Car {
    color: Box<dyn Shine>
}

struct CarDealership {
    cars: Vec<Car>
}

impl CarDealership {
    pub fn new_car(&mut self, color: Box<dyn Shine>) {
        self.cars.push(Car { color });
    }
}

// usage:
dealership.new_car(Box::new(Red {}));
dealership.new_car(Box::new(Green {}));
dealership.new_car(Box::new(Blue {}));

What if I didn't want any dynamic dispatch?

You can add this method to the trait:

trait Shine {
    fn shine(&self);
    fn add_car_to_dealership(car: Car<Self>, dealership: &mut ColoredCarsDealership);
}

Or you can split it into two traits if you would prefer.

2 Likes

The api allows code like below. What should the Car::new() do in this case?

struct Yellow;

impl Shine for Yellow {
    fn shine(&self) {
        println!("Yellowish shine.")
    }
}

Car::new(Yellow, &mut dealership);
1 Like

You have a good point but let's suppose that nobody does such a thing.

Possibly something like:

enum Colour {
    Red(Red),
    Green(Green),
    Blue(Blue),
}

impl Shine for Colour {
    fn shine(&self) {
        use Colour::*;
        match self {
            Red(c) => c.shine(),
            Green(c) => c.shine(),
            Blue(c) => c.shine(),
        }
    }
}

struct Car {
    colour: Colour,
}

struct Dealership {
    cars: Vec<Car>,  // No longer have to match type to add to correct Vec
}

This avoids dynamic dispatch, at the expense of a match and resulting boilerplate in Colour, and possible memory wastage in the Vec<> if your Shines are different sizes. If your colours are a closed set, then the compiler will help you ensure code validity using this method. [On the other hand, if the API is meant to be extensible (users can add their own Shines), then dynamic dispatch via Box<dyn Shine> is the best option.]

You could probably make such an API cleaner to use by implementing From<Red> etc. for Colour, and writing taking colour: impl Into<Colour> in function parameters.

This is a good idea!

Don't tell me that, say that to the compiler. :stuck_out_tongue:

@gkcjones 's code encode this information into the type system using the enum to let the compiler knows it.

3 Likes

I recon that you are right and that it is better to embed that information into the type system, in the end however, Kestrer's answer inspired my solution.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.