Search code examples
rustiteratortraitsdynamic-dispatch

How to use dynamic dispatch with a method which takes an iterator as a parameter?


I am writing a command line application in rust for processing audio from a sensor. I would like the user to be able to choose an algorithm or filter to apply from several options. I was hoping to use dynamic dispatch to switch out a struct which implements my filter trait at runtime. However, this is not allowed by the compiler, because one of the trait methods takes a generic parameter.

How could I implement this same functionality, without causing any compiler troubles? I know that an easy solution is to change the parameter of the process method to an array or a vector, but this is my last resort, as I would much prefer to take an iterator or an IntoIterator, as it is more general, and suits my specific needs.

Here is some code which demonstrates the problem.

trait SensorFilter {
    fn process(&self, sig: &mut impl Iterator<Item = f32>) -> Vec<f32>;
}

struct Alg1 {
    mul: f32,
}

struct Alg2 {
    add: f32,
}

impl SensorFilter for Alg1 {
    fn process(&self, sig: &mut impl Iterator<Item = f32>) -> Vec<f32> {
        sig.map(|x| x * self.mul).collect()
    }
}

impl SensorFilter for Alg2 {
    fn process(&self, sig: &mut impl Iterator<Item = f32>) -> Vec<f32> {
        sig.map(|x| x * self.add).collect()
    }
}

enum AlgChoice {
    Alg1,
    Alg2
}

fn main() {
    let choice = AlgChoice::Alg1; // user chooses via command-line.
    let mut sig = vec![0.,1.,2.,3.,4.,5.,6.].into_iter(); // iterator gets data from sensor.

    // This doesn't work, because my trait cannot be made into an object.
    let alg: &dyn SensorFilter = match choice {
        AlgChoice::Alg1 => Alg1{mul:0.3},
        _ => Alg2{add:1.2},
    };

    let result = alg.process(&mut sig);
    println!("{:?}",result);
}

Thanks :)


Solution

  • The trick here is to change your generic function parameter to a generic trait parameter:

    // Make the generic param into a type argument w/ constraints
    trait SensorFilter<I> where I: Iterator<Item = f32> {
        fn process(&self, sig: &mut I) -> Vec<f32>;
    }
    
    struct Alg1 {
        mul: f32,
    }
    
    struct Alg2 {
        add: f32,
    }
    
    // Implement trait for all I that match the iterator constraint
    impl<I: Iterator<Item = f32>> SensorFilter<I> for Alg1 {
        fn process(&self, sig: &mut I) -> Vec<f32> {
            sig.map(|x| x * self.mul).collect()
        }
    }
    
    impl<I: Iterator<Item = f32>> SensorFilter<I> for Alg2 {
        fn process(&self, sig: &mut I) -> Vec<f32> {
            sig.map(|x| x * self.add).collect()
        }
    }
    
    enum AlgChoice {
        Alg1,
        Alg2
    }
    
    fn main() {
        let choice = AlgChoice::Alg1; // user chooses via command-line.
        let mut sig = vec![0.,1.,2.,3.,4.,5.,6.].into_iter(); // iterator gets data from sensor.
    
        // Specify the type argument of your trait.
        let alg: &dyn SensorFilter<std::vec::IntoIter<f32>> = match choice {
            AlgChoice::Alg1 => &Alg1{mul:0.3},
            _ => &Alg2{add:1.2}, 
        };
    
        let result = alg.process(&mut sig);
        println!("{:?}",result);
    }