How can I avoid a ripple effect from changing a concrete struct to generic?

While generic types can seem to “infect” the rest of your code, that’s exactly why they are beneficial! The compiler knowledge about how big and specifically what type is used allow it to make better optimization decisions.

That being said, it can be annoying! If you have a small number of types that implement your trait, you can also construct an enum of those types and delegate to the child implementations:

enum MyBuilders {
    User(FromUser),
    File(FromFile),
}

impl ListBuilder for MyBuilders {
    fn build(&self, list: &mut Vec<String>) {
        use MyBuilders::*;
        match self {
            User(u) => u.build(list),
            File(f) => f.build(list),
        }
    }
}

// Support code

trait ListBuilder {
    fn build(&self, list: &mut Vec<String>);
}

struct FromUser;
impl ListBuilder for FromUser {
    fn build(&self, list: &mut Vec<String>) {}
}

struct FromFile;
impl ListBuilder for FromFile {
    fn build(&self, list: &mut Vec<String>) {}
}

Now the concrete type would be Conf<MyBuilders>, which you can use a type alias to hide.

I’ve used this to good effect when I wanted to be able to inject test implementations into code during testing, but had a fixed set of implementations that were used in the production code.

The enum_dispatch crate helps construct this pattern.

Leave a Comment