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

荒凉一梦 提交于 2020-12-06 12:21:00

问题


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 :)


回答1:


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);
}



回答2:


The simplest way to make SensorFilter object safe is to simply change process to accept dyn Iterator instead of impl Iterator:

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

If you couldn't do this, for example because Iterator were actually non-object-safe, you could instead extract the common, non-object-safe part into a second trait, and implement it automatically for everything that is SensorFilter:

// This trait is object-safe.
trait SensorFilter {
    fn filter(&self, x: f32) -> f32;
}

// This trait will not be object-safe because it uses generics.
trait Process {
    fn process<I: IntoIterator<Item = f32>>(&self, sig: I) -> Vec<f32>;
}

// The `?Sized` bound allows you to call `.process()` on `dyn SensorFilter`.
impl<T: ?Sized + SensorFilter> Process for T {
    fn process<I: IntoIterator<Item = f32>>(&self, sig: I) -> Vec<f32> {
        sig.into_iter().map(|x| self.filter(x)).collect()
    }
}

// ...

impl SensorFilter for Alg1 {
    fn filter(&self, x: f32) -> f32 {
        x * self.mul
    }
}

impl SensorFilter for Alg2 {
    fn filter(&self, x: f32) -> f32 {
        x * self.add
    }
}

Playground

Note that instead of Iterator I used IntoIterator, which is strictly more general.

A variation on this idea, when you can't easily remove the genericity from SensorFilter, is to use double dispatch: write SensorFilter to use dyn Iterator instead of impl Iterator, and then write a convenience trait that just wraps it with the specific type:

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

trait Process {
    fn process<I: IntoIterator<Item = f32>>(&self, sig: I) -> Vec<f32>;
}

impl<T: ?Sized + SensorFilter> Process for T {
    fn process<I: IntoIterator<Item = f32>>(&self, sig: I) -> Vec<f32> {
        self.process_dyn(&mut sig.into_iter())
    }
}


来源:https://stackoverflow.com/questions/63739976/how-to-use-dynamic-dispatch-with-a-method-which-takes-an-iterator-as-a-parameter

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!