Can I use traits to simplify enum matcher implementations?

I have a rust codebase that has a lot of code that looks like this:

struct FooA {}
impl struct FooA {
    pub fn run(&self) {}
}
struct FooB {}
impl struct FooB {
    pub fn run(&self) {}
}
enum Bars {
    BarA(FooA),
    BarB(FooB),
}
impl Bars {
    pub fn run(&self) {
        match self {
            BarA(wrapped: &FooA) => wrapped.run(),
            BarB(wrapped: &FooB) => wrapped.run(),
        }
    }
}

It's pretty clear that there's a trait here, that might look like this:

trait Runner {
    fn run(&self)
}

Both FooA and FooB could implement that, boom, easy peasy. However, it's the implementation of Runner in Bars that I'm hoping to simplify. As Bars gets bigger, we have to account for every single variant, but they will all essentially return wrapped.run() because they will all implement Runner.

Maybe I just am not using the right search term incantation, but I haven't found a right spelling that might make it so that the Runner implementation of Bars can just say "You'll always wrap an implementation of Runner, so call run on the unwrapped value. The best I can think of is maybe a macro.

There's no built-in language support for doing this, but the enum_dispatch crate is specifically designed for this use case.

2 Likes

Yes, this is a typical case where a #[derive] procedural macro comes useful. You can write your own that simply emits code for each individual case, and then implementing wrappers is as simple as #[derive(Runner)]. You might even find something in derive_more – if nothing else, example code.

If your design requirements permit you to skip the enum, then here is an alternative way without having to rely on enum and, consequently, match statements:

trait Runner {
    fn run(&self);
}

struct FooA {}
impl Runner for FooA {
    fn run(&self) {}
}

struct FooB {}
impl Runner for FooB {
    fn run(&self) {}
}

struct Bars<T>
where T: Runner 
{
    tag: String, // Here, you could use another type too
    item: T
}

impl<T: Runner> Bars<T> {
    pub fn run(&self) {
        self.item.run();
    }
}

pub fn main() {
    let my_bar = Bars {
        tag: "BarA".to_string(),
        item: FooA{}
    };
    my_bar.run();
}
2 Likes