我有一个程序,其中涉及检查复杂的数据结构以查看其是否有任何缺陷。 (这非常复杂,因此我要发布示例代码。)所有检查都不相互关联,并且都具有自己的模块和测试。

更重要的是,每张支票都有自己的错误类型,其中包含有关每个数字的支票失败方式的不同信息。我这样做不是只返回错误字符串,而是可以测试错误(这就是Error依赖PartialEq的原因)。

到目前为止我的代码

我具有CheckError的特征:

trait Check {
    type Error;
    fn check_number(&self, number: i32) -> Option<Self::Error>;
}

trait Error: std::fmt::Debug + PartialEq {
    fn description(&self) -> String;
}

还有两个示例检查,以及它们的错误结构。在此示例中,如果数字为负数甚至是偶数,我想显示错误:

#[derive(PartialEq, Debug)]
struct EvenError {
    number: i32,
}
struct EvenCheck;

impl Check for EvenCheck {
    type Error = EvenError;

    fn check_number(&self, number: i32) -> Option<EvenError> {
        if number < 0 {
            Some(EvenError { number: number })
        } else {
            None
        }
    }
}

impl Error for EvenError {
    fn description(&self) -> String {
        format!("{} is even", self.number)
    }
}

#[derive(PartialEq, Debug)]
struct NegativeError {
    number: i32,
}
struct NegativeCheck;

impl Check for NegativeCheck {
    type Error = NegativeError;

    fn check_number(&self, number: i32) -> Option<NegativeError> {
        if number < 0 {
            Some(NegativeError { number: number })
        } else {
            None
        }
    }
}

impl Error for NegativeError {
    fn description(&self) -> String {
        format!("{} is negative", self.number)
    }
}

我知道在此示例中,两个结构看起来相同,但是在我的代码中,有许多不同的结构,因此无法合并它们。最后,使用main函数示例,以说明我要执行的操作:
fn main() {
    let numbers = vec![1, -4, 64, -25];
    let checks = vec![
        Box::new(EvenCheck) as Box<Check<Error = Error>>,
        Box::new(NegativeCheck) as Box<Check<Error = Error>>,
    ]; // What should I put for this Vec's type?

    for number in numbers {
        for check in checks {
            if let Some(error) = check.check_number(number) {
                println!("{:?} - {}", error, error.description())
            }
        }
    }
}

您可以在the Rust playground中查看代码。

我尝试过的解决方案

我最接近解决方案的是删除关联的类型,并让检查返回Option<Box<Error>>。但是,我得到此错误:

error[E0038]: the trait `Error` cannot be made into an object
 --> src/main.rs:4:55
  |
4 |     fn check_number(&self, number: i32) -> Option<Box<Error>>;
  |                                                       ^^^^^ the trait `Error` cannot be made into an object
  |
  = note: the trait cannot use `Self` as a type parameter in the supertraits or where-clauses

因为PartialEq特性中的Error。到目前为止,Rust对我来说一直很棒,我真的希望我能够使类型系统支持这样的功能!

最佳答案

当您编写impl Check并使用具体类型专门化type Error时,您最终会得到不同的类型。

换句话说,Check<Error = NegativeError>Check<Error = EvenError>是静态不同的类型。尽管您可能希望Check<Error>来描述这两者,但是请注意,在Rust中,NegativeErrorEvenError并不是Error的子类型。他们保证可以实现Error特性定义的所有方法,但是对这些方法的调用将被静态分派(dispatch)给编译器创建的物理上不同的函数(每个函数都有NegativeError的版本,一个是EvenError的版本)。

因此,您不能将它们放在相同的Vec中,甚至不能装箱(如发现)。并不是要知道要分配多少空间,而是Vec要求其类型是同质的(您也不能拥有vec![1u8, 'a'],尽管char可表示为内存中的u8)。

正如您发现的那样,Rust的“擦除”某些类型信息并获得子类型的动态分配部分的方式是特征对象。

如果您想再次尝试特征对象方法,则可能需要做一些调整才能发现它更具吸引力。

  • 如果您在 Error 中使用std::error特性而不是您自己的版本,则可能会发现容易得多。

    您可能需要impl Display来使用动态构建的String创建描述,如下所示:
    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" }
    }
    
  • 现在,您可以删除关联的类型,并让Check返回特征对象:
    trait Check  {
        fn check_number(&self, number: i32) -> Option<Box<Error>>;
    }
    

    您的Vec现在具有可表达的类型:
    let mut checks: Vec<Box<Check>> = vec![
        Box::new(EvenCheck) ,
        Box::new(NegativeCheck) ,
    ];
    
  • 使用std::error::Error的最佳方法...

    就是现在您不需要使用PartialEq来了解所引发的错误。 Error具有各种类型的垂头丧气和类型检查,是否需要从trait对象中检索具体的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)
                }
            }
        }
    }
    

  • full example on the playground

    10-05 21:13
    查看更多