rustpattern-matchingcode-duplication

Match enum item tuples that implement the same trait


I have written a CLI program using clap.

In it, I have several structs and enums containing these structs for the specific sub-commands.

I now ran into the issue of a certain type of code duplication and wonder, whether the Rust language has some feature, that I am unaware of, that can deduplicate this.

Here's a MRE:

pub trait Runner {
    fn run(&self);
}

pub struct Foo;

impl Runner for Foo {
    fn run(&self) {
        println!("Fooing");
    }
}

pub struct Bar;

impl Runner for Bar {
    fn run(&self) {
        println!("Baring");
    }
}

pub enum Action {
    DoFoo(Foo),
    DoBar(Bar),
}

impl Runner for Action {
    fn run(&self) {
        match self {
            Self::DoFoo(runner) => runner.run(),
            Self::DoBar(runner) => runner.run(),
        }
    }
}

As you can see, the pattern matching on <Action as Runner>::run() has mostly the same code on all variants, since all of the enum variant's tuples contain exactly one element that implements Runner.

In this MRE it's quite trivial, but my actual code has currently seven variants.

Is it possible in Rust to deduplicate this pattern matching and use the fact that all variants contain a struct with the same trait?


Solution

  • When trying to mimic the dynamic-dispatch approach (relying on dyn traits) with enums, the enum_dispatch crate automates the boilerplate for the systematic forwarding of each method in the trait.

    Here is a simple example, based on yours. #[enum_dispatch] is used on the trait you intend to implement for many of your types. #[enum_dispatch(Runner)] instructs the following enum that each of its variants are types that implement this trait, then the forwarding of every trait method is automatically generated for all of these variants, as you would do manually (each method is a match statement considering all the variants in order to forward the call on it).

    use enum_dispatch::enum_dispatch;
    
    #[enum_dispatch]
    pub trait Runner {
        fn run(&self);
    }
    
    pub struct Foo;
    
    impl Runner for Foo {
        fn run(&self) {
            println!("Fooing");
        }
    }
    
    pub struct Bar;
    
    impl Runner for Bar {
        fn run(&self) {
            println!("Baring");
        }
    }
    
    #[enum_dispatch(Runner)]
    pub enum Action {
        Foo,
        Bar,
    }
    
    fn main() {
        let actions = [
            Action::from(Foo {}),
            Action::from(Bar {}),
            Action::from(Bar {}),
            Action::from(Foo {}),
        ];
        for a in actions.iter() {
            a.run();
        }
    }
    /*
    Fooing
    Baring
    Baring
    Fooing
    */