Collections 我如何拥有一个因关联类型不同而不同的对象集合?
我有一个程序,涉及检查复杂的数据结构,看看它是否有任何缺陷。(这相当复杂,所以我发布了示例代码。)所有的检查都彼此无关,并且都有自己的模块和测试 更重要的是,每个检查都有自己的错误类型,其中包含关于每个数字的检查失败原因的不同信息。我这样做,而不是仅仅返回一个错误字符串,以便测试错误(这就是为什么Collections 我如何拥有一个因关联类型不同而不同的对象集合?,collections,types,rust,Collections,Types,Rust,我有一个程序,涉及检查复杂的数据结构,看看它是否有任何缺陷。(这相当复杂,所以我发布了示例代码。)所有的检查都彼此无关,并且都有自己的模块和测试 更重要的是,每个检查都有自己的错误类型,其中包含关于每个数字的检查失败原因的不同信息。我这样做,而不是仅仅返回一个错误字符串,以便测试错误(这就是为什么error依赖PartialEq) 到目前为止我的代码 我有检查和错误的特点: trait Check { type Error; fn check_number(&self,
error
依赖PartialEq
)
到目前为止我的代码
我有检查
和错误
的特点:
trait Check {
type Error;
fn check_number(&self, number: i32) -> Option<Self::Error>;
}
trait Error: std::fmt::Debug + PartialEq {
fn description(&self) -> String;
}
您可以在中看到代码
我尝试过的解决方案
我找到的最接近解决方案是删除关联的类型,并让检查返回选项
。但是,我得到了以下错误:
error[E0038]:无法将特征'error'生成对象
-->src/main.rs:4:55
|
4 | fn检查_编号(&self,编号:i32)->选项;
|^^^^无法将特征“Error”制作成对象
|
=注意:trait不能在supertraits或where子句中将'Self'用作类型参数
由于
错误
特征中的PartialEq
。到目前为止,Rust对我来说非常棒,我真的希望我能够弯曲类型系统来支持这样的东西 我建议您进行一些重构
首先,我很确定,向量在锈迹中应该是同质的,所以没有办法为它们提供不同类型的元素。此外,你也不能通过贬低特征来将其简化为一个共同的基本特征(正如我记得的那样,在这方面有一个问题)
因此,我将使用代数类型和显式匹配来完成此任务,如下所示:
enum Checker {
Even(EvenCheck),
Negative(NegativeCheck),
}
let checks = vec![
Checker::Even(EvenCheck),
Checker::Negative(NegativeCheck),
];
关于错误处理,请考虑使用框架,这样您就可以在代码中涉及宏并将错误类型从一个转换为另一个。p> 我终于找到了一种我满意的方法。当您编写
impl Check
并将类型错误专门化为具体类型时,您将得到不同的类型,而不是框的向量
换句话说,Check
和Check
是静态不同的类型。尽管您可能希望Check
来描述这两者,但请注意,在RustNegativeError
和EvenError
中,它们不是Error
的子类型。它们保证实现由错误
特性定义的所有方法,但随后对这些方法的调用将静态地分派给编译器创建的物理上不同的函数(每个函数都有一个版本用于NegativeError
,一个版本用于EvenError
)
因此,您不能将它们放在相同的Vec
,甚至不能将它们装箱(正如您发现的那样)。与其说是知道要分配多少空间,不如说是Vec
要求它的类型是同质的(你也不能有Vec![1u8,'a']
,尽管char
在内存中可以表示为u8
)
正如您所发现的,Rust“擦除”一些类型信息并获得子类型的动态分派部分的方法是trait对象
如果你想再尝试一下trait对象的方法,你可能会发现它更吸引人一些调整
如果您在中使用错误
特性,而不是您自己的版本,您可能会发现这要容易得多
您可能需要impl Display
来使用动态构建的字符串创建描述,如下所示:
impl fmt::Display for EvenError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} is even", self.number)
}
}
impl Error for EvenError {
fn description(&self) -> &str { "even error" }
}
现在,您可以删除关联的类型并让检查返回一个trait对象:
trait Check {
fn check_number(&self, number: i32) -> Option<Box<Error>>;
}
使用std::error::error
的最佳部分
现在您不需要使用PartialEq
来理解抛出了什么错误<如果确实需要从trait对象中检索具体的Error
类型,则code>Error
具有各种类型的向下转换和类型检查
for number in numbers {
for check in &mut checks {
if let Some(error) = check.check_number(number) {
println!("{}", error);
if let Some(s_err)= error.downcast_ref::<EvenError>() {
println!("custom logic for EvenErr: {} - {}", s_err.number, s_err)
}
}
}
}
用于数字中的数字{
用于签入和mut检查{
如果让一些(错误)=检查。检查编号(编号){
println!(“{}”,错误);
如果让一些(s_err)=错误。向下投射参考::(){
println!(“EvenErr的自定义逻辑:{}-{}”,s_err.number,s_err)
}
}
}
}
嗨,谢谢你的建议。向量在Rust中是同质的,这是正确的,但是可以将元素转换为相同的类型(例如长方体特征对象),以使它们都适合。我考虑创建一个新的enum
,其中包含所有可能的检查器,但我计划有几十个这样的检查器,每个检查器都有许多行代码,代码很快就会变得重复和笨拙。基本上,我想要一种使用trait对象的方法来为我编写所有这些代码!我不相信,在您的案例中,有一种方法可以将所有元素转换为相同的类型。试想一下,如果你是一个编译器,那么你会为check分配多少堆栈空间。check\u number(number)
result在循环中?它肯定应该是一个恒定大小的东西,所以我们再次讨论了联合类型(enum)或指针,就像您的选项
版本一样。所以也许你应该考虑去掉<代码> Pixaleq impl fmt::Display for EvenError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} is even", self.number)
}
}
impl Error for EvenError {
fn description(&self) -> &str { "even error" }
}
trait Check {
fn check_number(&self, number: i32) -> Option<Box<Error>>;
}
let mut checks: Vec<Box<Check>> = vec![
Box::new(EvenCheck) ,
Box::new(NegativeCheck) ,
];
for number in numbers {
for check in &mut checks {
if let Some(error) = check.check_number(number) {
println!("{}", error);
if let Some(s_err)= error.downcast_ref::<EvenError>() {
println!("custom logic for EvenErr: {} - {}", s_err.number, s_err)
}
}
}
}