【问题标题】:How to test for equality between trait objects?如何测试特征对象之间的相等性?
【发布时间】:2014-08-16 11:13:54
【问题描述】:

编者注:此代码示例来自 Rust 1.0 之前的版本,在语法上不是有效的 Rust 1.0 代码。此代码的更新版本会产生不同的错误,但答案仍然包含有价值的信息。

在以下情况下,我们似乎无法测试相等性。为什么是这样?有解决方法吗? (我使用的是 Rust 0.11)。

trait A: PartialEq {}

#[deriving(PartialEq)]
enum T {Ta, Tb}

impl A for T {}

fn main() {
  assert!(Ta == Ta);
  assert!(Ta != Tb);
  assert!(some_fn(&Ta, &Ta));
  assert!(!some_fn(&Ta, &Tb));
}

fn some_fn(an_a: &A, another_a: &A) -> bool {
    an_a == another_a
// ERROR ^~~~~~~~~~~~ binary operation `==` cannot be applied to type `&A`
}

fn another_fn(an_a: &A + PartialEq, another_a: &A + PartialEq) -> bool {
               // ERROR: ^~~~~~~~~ only the builtin traits can be used as closure or object bounds
    an_a == another_a
}

【问题讨论】:

    标签: rust


    【解决方案1】:

    这是PartialEq trait 的定义:

    pub trait PartialEq<Rhs = Self> 
    where
        Rhs: ?Sized, 
    {
        fn eq(&self, other: &Rhs) -> bool;
    
        fn ne(&self, other: &Rhs) -> bool { ... }
    }
    

    注意Self 参数类型。这意味着eq()ne() 方法接受与实现者类型相同的参数。例如:

    impl PartialEq for i32 {
        fn eq(&self, other: &i32) -> bool { ... }
    }
    
    impl PartialEq for String {
        fn eq(&self, other: &String) -> bool { ... }
    }
    

    注意other 的类型如何变化以反映PartialEq 的实现类型。

    这就是问题所在。在 trait 对象中,实际类型是 erased 并且在运行时不可用。这意味着不可能从 trait 对象中获得对具体类型的引用;特别是,在您的示例中,您不能从 &amp;A 转到 &amp;T

    这意味着不可能在 trait 对象上调用接受或返回 Self 类型的方法。事实上,这些方法总是需要一个 concrete 类型,但如果你只有一个 trait 对象,就没有 具体类型,而且这种方法不可能在任何明智的做法。

    【讨论】:

    • 另外,如果您尝试将 &A 的 PartialEq 实现为 (*self).eq(*other),它会抱怨“无法通过对象 [E0038] 调用其类型包含自类型的方法”。
    • 这是有道理的。我想我必须先动态转换。不确定这在 Rust 中是否可行?
    • @Samuel,不,一般来说不可能;您不能从特征对象向下转换为具体类型,因为它已被完全擦除。然而,有一个特性,Any,它允许向下转换,但它只能用于'static 类型(不包含非'static 生命周期的引用)。看看吧,也许它可以帮助你完成任务。
    • @VladimirMatveev,是的,我最终按照您的建议使用了 Any。看我的回答。感谢提供线索!
    【解决方案2】:

    通过help from Vladimir Matveev,我想出了如何使用Any 将我的特征向下转换为具体类型并测试结果值是否相等:

    // `Any` allows us to do dynamic typecasting.
    use std::any::Any;
    
    trait A {
        // An &Any can be cast to a reference to a concrete type.
        fn as_any(&self) -> &dyn Any;
    
        // Perform the test.
        fn equals_a(&self, _: &dyn A) -> bool;
    }
    
    #[derive(Debug, PartialEq)]
    enum T {
        Ta,
        Tb,
    }
    
    // Implement A for all 'static types implementing PartialEq.
    impl<S: 'static + PartialEq> A for S {
        fn as_any(&self) -> &dyn Any {
            self
        }
    
        fn equals_a(&self, other: &dyn A) -> bool {
            // Do a type-safe casting. If the types are different,
            // return false, otherwise test the values for equality.
            other
                .as_any()
                .downcast_ref::<S>()
                .map_or(false, |a| self == a)
        }
    }
    
    fn main() {
        assert_eq!(T::Ta, T::Ta);
        assert_ne!(T::Ta, T::Tb);
        assert!(some_fn(&T::Ta, &T::Ta));
        assert!(!some_fn(&T::Ta, &T::Tb));
    }
    
    fn some_fn(an_a: &dyn A, another_a: &dyn A) -> bool {
        // It works!
        an_a.equals_a(another_a)
    }
    

    【讨论】:

    【解决方案3】:

    在某些 trait 对象的情况下,您希望根据通过 trait 公开的一些属性来比较它们。您可以通过在 trait 类型上实现方法 来实现这一点:

    trait A {
        fn id(&self) -> i32;
    }
    
    impl PartialEq for dyn A + '_ {
        fn eq(&self, other: &Self) -> bool {
            self.id() == other.id()
        }
    }
    
    impl Eq for dyn A + '_ {}
    
    fn some_fn(an_a: &dyn A, another_a: &dyn A) -> bool {
        an_a == another_a
    }
    

    这并没有直接解决想要委托回底层类型PartialEq实现的原始案例,但您可以结合existing solution

    impl PartialEq for dyn A + '_ {
        fn eq(&self, other: &Self) -> bool {
            self.equals_a(other)
        }
    }
    

    另见:

    【讨论】:

    • 我很惊讶!它解决了我的情况,非常简单,经过大量的拉扯。
    猜你喜欢
    • 2011-06-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多