简体   繁体   English

在盒装特征上实现PartialEq

[英]Implementing PartialEq on a boxed trait

I have a Rust program which contains a number of different structs which all implement a trait called ApplyAction . 我有一个Rust程序,它包含许多不同的结构,它们都实现了一个叫做ApplyAction的特性。 Another struct, ActionList , contains a vector of boxed objects which implement ApplyAction . 另一个结构ActionList包含一个实现ApplyAction的盒装对象向量。 I would like to create some unit tests which compare ActionList s with one another. 我想创建一些单元测试,将ActionList与彼此进行比较。

There are a few different SO questions which deal with PartialEq on boxed traits, and I've used these to get some way towards an implementation. 有几个不同的SO问题在盒装特征上处理PartialEq ,我已经用它们来实现一些实现。 However, in the (simplified) code below (and on the Playground ), the assertions in main() fail because the type ids of the objects passed to eq() differ. 但是,在下面的(简化)代码(和Playground )中, main()的断言失败,因为传递给eq()的对象的类型id不同。 Why? 为什么?

Also, this seems extremely complicated for such a simple use case -- is there an easier way to do this? 此外,对于这样一个简单的用例来说,这似乎非常复杂 - 有更简单的方法吗?

use std::any::TypeId;
use std::boxed::Box;
use std::fmt;
use std::mem::transmute;

#[derive(Debug, Eq, PartialEq)]
pub struct MyAction<T: fmt::Debug> {
    label: T,
}

impl<T: fmt::Debug> MyAction<T> {
    pub fn new(label: T) -> MyAction<T> {
        MyAction { label: label }
    }
}

pub trait ApplyAction<T: fmt::Debug + PartialEq>: fmt::Debug {
    fn get_type(&self) -> TypeId;
    fn is_eq(&self, other: &ApplyAction<T>) -> bool;
}

impl<T: fmt::Debug + Eq + 'static> ApplyAction<T> for MyAction<T> {
    fn get_type(&self) -> TypeId {
        TypeId::of::<MyAction<T>>()
    }

    fn is_eq(&self, other: &ApplyAction<T>) -> bool {
        if other.get_type() == TypeId::of::<Self>() {
            // Rust thinks that self and other are different types in the calls below.
            let other_ = unsafe { *transmute::<&&ApplyAction<T>, &&Self>(&other) };
            self.label == other_.label
        } else {
            false
        }
    }
}

impl<T: fmt::Debug + Eq + PartialEq + 'static> PartialEq for ApplyAction<T> {
    fn eq(&self, other: &ApplyAction<T>) -> bool {
        if other.get_type() == TypeId::of::<Self>() {
            self.is_eq(other)
        } else {
            false
        }
    }
}

#[derive(Debug)]
pub struct ActionList<T: fmt::Debug> {
    actions: Vec<Box<ApplyAction<T>>>,
}

impl<T: fmt::Debug + PartialEq> ActionList<T> {
    pub fn new() -> ActionList<T> {
        ActionList { actions: vec![] }
    }
    pub fn push<A: ApplyAction<T> + 'static>(&mut self, action: A) {
        self.actions.push(Box::new(action));
    }
}

impl<T: fmt::Debug + Eq + PartialEq + 'static> PartialEq for ActionList<T> {
    fn eq(&self, other: &ActionList<T>) -> bool {
        for (i, action) in self.actions.iter().enumerate() {
            if **action != *other.actions[i] {
                return false;
            }
        }
        true
    }
}

fn main() {
    let mut script1: ActionList<String> = ActionList::new();
    script1.push(MyAction::new("foo".to_string()));
    let mut script2: ActionList<String> = ActionList::new();
    script2.push(MyAction::new("foo".to_string()));
    let mut script3: ActionList<String> = ActionList::new();
    script3.push(MyAction::new("bar".to_string()));
    assert_eq!(script1, script2);
    assert_ne!(script1, script3);
}

In the impl<...> PartialEq for ApplyAction<T> you used TypeId::of::<Self>() ; impl<...> PartialEq for ApplyAction<T>你使用了TypeId::of::<Self>() ; ie the type of the unsized trait object. 即未确定的特征对象的类型。 That isn't what you wanted; 那不是你想要的; but remove the if and directly call self.is_eq(other) , and your code should be working. 但删除if并直接调用self.is_eq(other) ,你的代码应该正常工作。

Sadly your example requires a lot of code to implement ApplyAction<T> for MyAction<T> - and again for each other action type you might want to use. 遗憾的是,您的示例需要大量代码才能ApplyAction<T> for MyAction<T>实现ApplyAction<T> for MyAction<T> - 并且还需要您可能想要使用的其他每种操作类型。

I tried to remove that overhead, and with nightly features it is completely gone (and otherwise only a small stub remains): 我试图消除这种开销,并且夜间功能完全消失(否则只剩下一个小存根):

Playground 操场

// see `default impl` below
#![feature(specialization)]

// Any::<T>::downcast_ref only works for special trait objects (`Any` and
// `Any + Send`); having a trait `T` derive from `Any` doesn't allow you to
// coerce ("cast") `&T` into `&Any` (that might change in the future).
//
// Implementing a custom `downcast_ref` which takes any
// `T: Any + ?Sized + 'static` as input leads to another problem: if `T` is a
// trait that didn't inherit `Any` you still can call `downcast_ref`, but it
// won't work (it will use the `TypeId` of the trait object instead of the
// underlying (sized) type).
//
// Use `SizedAny` instead: it's only implemented for sized types by default;
// that prevents the problem above, and we can implement `downcast_ref` without
// worrying.

mod sized_any {
    use std::any::TypeId;

    // don't allow other implementations of `SizedAny`; `SizedAny` must only be
    // implemented for sized types.
    mod seal {
        // it must be a `pub trait`, but not be reachable - hide it in
        // private mod.
        pub trait Seal {}
    }

    pub trait SizedAny: seal::Seal + 'static {
        fn get_type_id(&self) -> TypeId {
            TypeId::of::<Self>()
        }
    }

    impl<T: 'static> seal::Seal for T {}
    impl<T: 'static> SizedAny for T {}

    // `SizedAny + ?Sized` means it can be a trait object, but `SizedAny` was
    // implemented for the underlying sized type.
    pub fn downcast_ref<From, To>(v: &From) -> Option<&To>
    where
        From: SizedAny + ?Sized + 'static,
        To: 'static,
    {
        if TypeId::of::<To>() == <From as SizedAny>::get_type_id(v) {
            Some(unsafe { &*(v as *const From as *const To) })
        } else {
            None
        }
    }
}
use sized_any::*;

use std::boxed::Box;
use std::fmt;

// `ApplyAction`

fn foreign_eq<T, U>(a: &T, b: &U) -> bool
where
    T: PartialEq + 'static,
    U: SizedAny + ?Sized + 'static,
{
    if let Some(b) = downcast_ref::<U, T>(b) {
        a == b
    } else {
        false
    }
}

pub trait ApplyAction<T: 'static>: fmt::Debug + SizedAny + 'static {
    fn foreign_eq(&self, other: &ApplyAction<T>) -> bool;
}

// requires `#![feature(specialization)]` and a nightly compiler.
// could also copy the default implementation manually to each `impl` instead.
//
// this implementation only works with sized `A` types; we cannot make 
// `ApplyAction<T>` inherit `Sized`, as that would destroy object safety.
default impl<T: 'static, A: PartialEq + 'static> ApplyAction<T> for A {
    fn foreign_eq(&self, other: &ApplyAction<T>) -> bool {
        foreign_eq(self, other)
    }
}

impl<T: 'static> PartialEq for ApplyAction<T> {
    fn eq(&self, other: &ApplyAction<T>) -> bool {
        self.foreign_eq(other)
    }
}

// `MyAction`

#[derive(Debug, Eq, PartialEq)]
pub struct MyAction<T: fmt::Debug> {
    label: T,
}

impl<T: fmt::Debug> MyAction<T> {
    pub fn new(label: T) -> MyAction<T> {
        MyAction { label: label }
    }
}

impl<T: fmt::Debug + PartialEq + 'static> ApplyAction<T> for MyAction<T> {}

// `ActionList`

#[derive(Debug)]
pub struct ActionList<T> {
    actions: Vec<Box<ApplyAction<T>>>,
}

impl<T: 'static> ActionList<T> {
    pub fn new() -> ActionList<T> {
        ActionList { actions: vec![] }
    }

    pub fn push<A: ApplyAction<T> + 'static>(&mut self, action: A) {
        self.actions.push(Box::<A>::new(action));
    }
}

impl<T: 'static> PartialEq for ActionList<T> {
    fn eq(&self, other: &ActionList<T>) -> bool {
        if self.actions.len() != other.actions.len() {
            return false;
        }
        for (i, action) in self.actions.iter().enumerate() {
            if **action != *other.actions[i] {
                return false;
            }
        }
        true
    }
}

// `main`

fn main() {
    let mut script1: ActionList<String> = ActionList::new();
    script1.push(MyAction::new("foo".to_string()));
    let mut script2: ActionList<String> = ActionList::new();
    script2.push(MyAction::new("foo".to_string()));
    let mut script3: ActionList<String> = ActionList::new();
    script3.push(MyAction::new("bar".to_string()));
    assert_eq!(script1, script2);
    assert_ne!(script1, script3);
}

See also: 也可以看看:

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM