Rust中如何实现容纳不同嵌套泛型/trait对象的Processor的Vec?
多输入类型Processor的统一调度实现问题
我正在尝试实现一种模式,让不同的Processor可以指定自身接收的输入类型,并生成统一的输出(当前为固定类型,完成当前实现后计划将其泛型化)。
最小示例代码
use std::convert::From; use processor::NoOpProcessor; use self::{ input::{Input, InputStore}, output::UnifiedOutput, processor::{MultiplierProcessor, Processor, StringProcessor}, }; mod input { use std::collections::HashMap; #[derive(Debug)] pub struct Input<T>(pub T); #[derive(Default)] pub struct InputStore(HashMap<String, String>); impl InputStore { pub fn insert<K, V>(mut self, key: K, value: V) -> Self where K: ToString, V: ToString, { let key = key.to_string(); let value = value.to_string(); self.0.insert(key, value); self } pub fn get<K, V>(&self, key: K) -> Option<Input<V>> where K: ToString, for<'a> &'a String: Into<V>, { let key = key.to_string(); self.0.get(&key).map(|value| Input(value.into())) } } } mod processor { use super::{input::Input, output::UnifiedOutput}; use super::I32Input; pub struct NoOpProcessor; pub trait Processor { type I; fn process(&self, input: &Input<Self::I>) -> UnifiedOutput; } impl Processor for NoOpProcessor { type I = I32Input; fn process(&self, input: &Input<Self::I>) -> UnifiedOutput { UnifiedOutput(input.0 .0) } } pub struct MultiplierProcessor(pub i32); impl Processor for MultiplierProcessor { type I = I32Input; fn process(&self, input: &Input<Self::I>) -> UnifiedOutput { UnifiedOutput(input.0 .0 * self.0) } } pub struct StringProcessor; impl Processor for StringProcessor { type I = String; fn process(&self, input: &Input<Self::I>) -> UnifiedOutput { UnifiedOutput(input.0.parse().unwrap()) } } } mod output { #[derive(Debug)] pub struct UnifiedOutput(pub i32); } pub fn main() { let input_store = InputStore::default() .insert("input_a", 123) .insert("input_b", 567) .insert("input_c", "789"); let processors = { let mut labelled_processors = Vec::new(); // let mut labelled_processors: Vec<LabelledProcessor<Input<>>> = Vec::new(); // What's the correct type? labelled_processors.push(LabelledProcessor("input_a", Box::new(NoOpProcessor))); labelled_processors.push(LabelledProcessor( "input_b", Box::new(MultiplierProcessor(3)), )); // labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor))); labelled_processors }; for processor in processors { let output = retrieve_input_and_process(&input_store, processor); println!("{:?}", output); } } #[derive(Debug)] pub struct I32Input(pub i32); impl From<&String> for I32Input { fn from(s: &String) -> Self { Self(s.parse().unwrap()) } } struct LabelledProcessor<I>(&'static str, Box<dyn Processor<I = I>>) where for<'a> &'a String: Into<I>; fn retrieve_input_and_process<T>( store: &InputStore, processor: LabelledProcessor<T>, ) -> UnifiedOutput where for<'a> &'a String: Into<T>, { let input = store.get(processor.0).unwrap(); processor.1.process(&input) }
编译错误信息
当取消注释// labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor)));时,会触发以下编译错误:
error[E0271]: type mismatch resolving `<attempt2::processor::StringProcessor as attempt2::processor::Processor>::I == attempt2::I32Input` --> src/attempt2.rs:101:63 | 101 | labelled_processors.push(LabelledProcessor("input_c", Box::new(StringProcessor))); | ^^^^^^^^^^^^^^^^^^^^^^^^^ type mismatch resolving `<attempt2::processor::StringProcessor as attempt2::processor::Processor>::I == attempt2::I32Input` | note: expected this to be `attempt2::I32Input` --> src/attempt2.rs:75:18 | 75 | type I = String; | ^^^^^^ = note: required for the cast from `attempt2::processor::StringProcessor` to the object type `dyn attempt2::processor::Processor<I = attempt2::I32Input>`
问题分析与困惑
我清楚问题所在:labelled_processors这个Vec要求所有元素的类型必须相同,但我不知道该如何解决。我尝试过进一步利用动态调度(比如把LabelledProcessor改成struct LabelledProcessor(&'static str, Box<dyn Processor<dyn Input>>);),但这类修改又引发了其他类型系统问题。
网上找到的其他答案通常只涉及let vec_x: Vec<Box<dyn SomeTrait>>这类基础动态调度场景,没有覆盖这种嵌套泛型/trait对象的复杂情况。这让我怀疑是不是有什么简单的解决方案被我遗漏了,或者是否需要采用完全不同的模式来实现目标。
我知道可以用枚举,但这意味着所有用例都必须在同一个模块内定义,无法在外部模块中扩展输入、输出和Processor。现在我有些困惑。
--- 补充说明 ---
- 这只是一个示例,
InputStore将所有内容转换为String只是实现细节,核心是要体现“类型需符合某trait才能被接受”的概念,选择String只是为了简化。
内容的提问来源于stack exchange,提问作者Clown
相关产品推荐
相关产品推荐

