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.