#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())
}
}