How could I implement a more accurate comparison?

I have a container structure used to contain some value. Now I want to implement the PartialEq trait for my container structure, but I don't have a good idea to implement it.

Code below:

use std::any::{type_name, Any, TypeId};
use std::fmt;
use std::sync::Arc;

#[derive(Clone)]
struct Container {
    inner: Arc<dyn Any + Send + Sync>,
    inner_type_name: &'static str,
}

impl Container {
    fn new<T>(result: T) -> Self
    where
        T: Any + Send + Sync,
    {
        Self {
            inner: Arc::new(result),
            inner_type_name: type_name::<T>(),
        }
    }
    
    fn type_id(&self) -> TypeId {
        self.inner.as_ref().type_id()
    }
}

impl fmt::Debug for Container {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Container<{}>", self.inner_type_name)
    }
}

impl PartialEq for Container {
    fn eq(&self, other: &Self) -> bool {
        // How could I implement a more accurate comparison?
        self.type_id() == other.type_id()
    }
}

fn main() {
    let a = Container::new(1);
    println!("a: {:?}", a);
    let b = Container::new(2.0);
    println!("b: {:?}", b);
    let c = Container::new(2);
    println!("c: {:?}", c);

    assert_ne!(a, b);
    assert_eq!(a, c);
}

playground

Thanks in advance for any suggestions.

Here's one way: Rust Playground

2 Likes

Thanks for your help. Using the EqHelper trait can solve most situations, but it doesn't work for some value wrapped by the Mutex. If I pass a Mutex wrapped object, the compiler will complain the PartialEq trait is not implemented for Mutex.

For example:

fn main() {
    let e = Container::new(Mutex::new(1));
    let f = Container::new(Mutex::new(1));
    assert_eq!(e, f);
}

Error outputs:

error[E0277]: can't compare `Mutex<{integer}>` with `Mutex<{integer}>`
  --> src/main.rs:79:28
   |
38 |     fn new<T>(result: T) -> Self
   |        --- required by a bound in this
39 |     where
40 |         T: Any + Send + Sync + PartialEq,
   |                                --------- required by this bound in `Container::new`
...
79 |     let e = Container::new(Mutex::new(1));
   |                            ^^^^^^^^^^^^^ no implementation for `Mutex<{integer}> == Mutex<{integer}>`
   |
   = help: the trait `PartialEq` is not implemented for `Mutex<{integer}>`

Rust Playground

BTW, how if I compare the string format of both values? :eyes:

impl Container {
    fn new<T>(result: T) -> Self
    where
        T: Any + Send + Sync + fmt::Debug,
    {
        let debug_string_format = format!("{:?}", result);
        Self {
            inner: Arc::new(result),
            inner_type_name: type_name::<T>(),
            inner_string_format: debug_string_format,
        }
    }
    
    fn type_id(&self) -> TypeId {
        self.inner.as_ref().type_id()
    }
}

impl PartialEq for Container {
    fn eq(&self, other: &Self) -> bool {
        // How if I compare the string format of values?
        self.type_id() == other.type_id()
            && self.inner_string_format == other.inner_string_format
    }
}

fn main() {
    let e = Container::new(Mutex::new(1));
    println!("e: {:?}", e);
    let f = Container::new(Mutex::new(1.0));
    println!("f: {:?}", f);
    let g = Container::new(Mutex::new(1));
    println!("g: {:?}", g);
    assert_ne!(e, f);
    assert_eq!(e, g);
}

Rust Playground

You should explain when your Container has to be equal and when not :wink:
@alice 's Container is different equal to yours.
You could also count up an id something like

lazy_static! {
    static ref ID_COUNT: AtomicUSize = AtomicUsize::new(0);
}

struct Container {
    id: usize,
    ...
}

impl Container {
    fn new<T>(result: T) -> Self
    where
        T: Any + Send + Sync,
    {
        Self {
            id: ID_COUNT.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |x| Some(x + 1)).unwrap(),
...

Or use an random id.
Depends on what is the requirement of your "Container".

1 Like

A trick I've found useful when working with Any is to create a vtable which is specific to the T that is being erased. That way you capture the functionality you need when you know the concrete type (i.e. Container::new() and can carry it around with you after the type has been forgotten.

For example, say we were wrapping a Box<dyn Any> and it needed to be Any for some reason (e.g. that's how it gets used downstream) so we can't use @alice's ContainerAny trick to merge the Any and EqHelper traits. Here is how I would make a vtable for Debug printing our object.

#[derive(Copy, Clone)]
struct VTable {
    type_id: TypeId,
    type_name: &'static str,
    debug: fn(&dyn Any) -> &dyn Debug,
}

impl VTable {
    fn for_type<T>() -> Self
    where
        T: Any + Debug + 'static,
    {
        VTable {
            type_id: TypeId::of::<T>(),
            type_name: any::type_name::<T>(),
            debug: |value: &dyn Any| -> &dyn Debug { value.downcast_ref::<T>().unwrap() },
        }
    }
}

Which then gets implemented like so:

#[derive(Clone)]
struct Container {
    inner: Arc<dyn Any + Send + Sync>,
    vtable: VTable,
}

impl Container {
    fn new<T>(result: T) -> Self
    where
        T: Any + Debug + Send + Sync,
    {
        let inner = Arc::new(result);
        let vtable = VTable::for_type::<T>();
        Container { inner, vtable }
    }
}

impl Debug for Container {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        (self.vtable.debug)(&*self.inner).fmt(f)
    }
}

(playground)

I'll leave the PartialEq version as an exercise to the reader, but as a hint I'd probably give VTable a partial_eq: fn(&dyn Any, &dyn Any) -> bool field :wink:

1 Like

Thanks.

I want to compare both things contained into Container have same types and equal values. I try to implement the eq like below:

impl PartialEq for Container {
    fn eq(&self, other: &Self) -> bool {
        self.type_id() == other.type_id() && self.inner == other.inner
    }
}

So I can compare both Container items like this:

let a = Container::new(1);
let b = Container::new(1);
let c = Container::new(1.0);
let d = Container::new(2);
assert_eq!(a, b);
assert_ne!(a, c);
assert_ne!(a, d);

But it doesn't work because the type of inner is being erased.

Thanks for your useful trick. :grin:

I try to implement the PartialEq for Container that has a vtable field:

impl Container {
    fn new<T>(result: T) -> Self
    where
        T: Any + Debug + Send + Sync + PartialEq,
    {
        let inner = Arc::new(result);
        let vtable = VTable::for_type::<T>();
        Container { inner, vtable }
    }
}

impl PartialEq for Container {
    fn eq(&self, other: &Self) -> bool {
        (self.vtable.partial_eq)(&*self.inner, &*other.inner)
    }
}

And the partial_eq of VTable:

#[derive(Copy, Clone)]
struct VTable {
    type_id: TypeId,
    type_name: &'static str,
    debug: fn(&dyn Any) -> &dyn Debug,
    partial_eq: fn(&dyn Any, &dyn Any) -> bool,
}

impl VTable {
    fn for_type<T>() -> Self
    where
        T: Any + Debug + PartialEq + 'static,
    {
        VTable {
            type_id: TypeId::of::<T>(),
            type_name: any::type_name::<T>(),
            debug: |value: &dyn Any| -> &dyn Debug { value.downcast_ref::<T>().unwrap() },
            partial_eq: |left: &dyn Any, right: &dyn Any| -> bool {
                match (left.downcast_ref::<T>(), right.downcast_ref::<T>()) {
                    (Some(l), Some(r)) => l == r,
                    _ => false,
                }
            },
        }
    }
}

And this version PartialEq can satisfy the primitive type, but not for compound values. I think my partial_eq doesn't a good implementation.

fn main() {
    let a = Container::new(1);
    let b = Container::new(1);
    let c = Container::new(1.0);
    let d = Container::new(2);
    assert_eq!(a, b);
    assert_ne!(a, c);
    assert_ne!(a, d);

    /* Cannot compile
    let e = Container::new(Mutex::new(1));
    let f = Container::new(Mutex::new(1));
    assert_eq!(e, f);
    */
}

Playground

Looking forward to your comments. :grinning_face_with_smiling_eyes:

Your problem is that Mutex<T> doesn't implement PartialEq.

Sure we could write the naive implementation which locks a and b then uses == on the items inside, but that means comparing a Container with itself will trigger a deadlock.

Ok, it makes sense. The PartialEq bound is a logical choice if we want to compare the values contained into Container. This bound ensures we can compare both values.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.