Как использовать динамическую отправку с методом, который принимает итератор в качестве параметра?

#generics #rust #traits #dynamic-dispatch #trait-objects

#Ржавчина #итератор #Трейты #динамическая отправка

Вопрос:

Я пишу приложение командной строки в rust для обработки звука с датчика. Я хотел бы, чтобы пользователь мог выбрать алгоритм или фильтр для применения из нескольких вариантов. Я надеялся использовать динамическую отправку для переключения структуры, которая реализует мою характеристику фильтра во время выполнения. Однако компилятор не разрешает это, поскольку один из методов trait принимает общий параметр.

Как я мог бы реализовать эту же функциональность, не вызывая никаких проблем с компилятором? Я знаю, что простым решением является изменение параметра метода process на массив или вектор, но это мое последнее средство, поскольку я бы предпочел использовать iterator или IntoIterator, поскольку он более общий и соответствует моим конкретным потребностям.

Вот некоторый код, который демонстрирует проблему.

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

struct Alg1 {
    mul: f32,
}

struct Alg2 {
    add: f32,
}

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

impl SensorFilter for Alg2 {
    fn process(amp;self, sig: amp;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: amp;dyn SensorFilter = match choice {
        AlgChoice::Alg1 => Alg1{mul:0.3},
        _ => Alg2{add:1.2},
    };

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

  

Спасибо 🙂

Ответ №1:

Хитрость здесь в том, чтобы изменить ваш общий параметр функции на общий параметр признака:

 // Make the generic param into a type argument w/ constraints
trait SensorFilter<I> where I: Iterator<Item = f32> {
    fn process(amp;self, sig: amp;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(amp;self, sig: amp;mut I) -> Vec<f32> {
        sig.map(|x| x * self.mul).collect()
    }
}

impl<I: Iterator<Item = f32>> SensorFilter<I> for Alg2 {
    fn process(amp;self, sig: amp;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: amp;dyn SensorFilter<std::vec::IntoIter<f32>> = match choice {
        AlgChoice::Alg1 => amp;Alg1{mul:0.3},
        _ => amp;Alg2{add:1.2}, 
    };

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

Ответ №2:

Самый простой способ сделать SensorFilter объект безопасным — просто изменить process на accept dyn Iterator вместо impl Iterator :

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

Если вы не можете этого сделать, например, потому Iterator , что они фактически не являются объектно-безопасными, вы могли бы вместо этого извлечь общую, не защищенную от объектов часть во второй признак и реализовать ее автоматически для всего, что есть SensorFilter :

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

// This trait will not be object-safe because it uses generics.
trait Process {
    fn process<I: IntoIterator<Item = f32>>(amp;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>>(amp;self, sig: I) -> Vec<f32> {
        sig.into_iter().map(|x| self.filter(x)).collect()
    }
}

// ...

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

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

Игровая площадка

Обратите внимание, что вместо Iterator I используется IntoIterator , что является строго более общим.

Вариант этой идеи, когда вы не можете легко удалить обобщенность из SensorFilter , заключается в использовании двойной отправки: SensorFilter вместо записи dyn Iterator использовать impl Iterator , а затем написать удобный признак, который просто обертывает его определенным типом:

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

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

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