How to support `derive(PartialEq)` for "dyn PartialEq"`?

I.e. why isn't the following allowed,

trait A: PartialEq {}

#[derive(PartialEq)]
struct B {
    field: Box<dyn A>
}

imo currently it is a pain to use trait objects when trying to build structs that have them (similar problems exist for Clone, Hash, etc.).

I do not see why such implementations should not be supported since they are un-ambiguous, but maybe I am missing something?

See also

The problem isn't the derive, it's dyn PartialEq.

The trick is that PartialEq is actually shorthand for PartialEq<Self>. So, with dyn PartialEq<Self>, what's Self? You don't know; it's whatever the dynamic type is!

If you remove the dummy trait A (which I'm fairly sure was added to address this error), the error is a bit clearer:

error[E0393]: the type parameter `Rhs` must be explicitly specified
   --> src/lib.rs:1:21
    |
1   |   fn check(_: Box<dyn PartialEq>) {}
    |                       ^^^^^^^^^ help: set the type parameter to the desired type: `PartialEq<Rhs>`
    |
    = note: because of the default `Self` reference, type parameters must be specified on object types

So what you want probably looks something like instead

trait DynPartialEq {
    fn eq(&self, other: &dyn DynPartialEq) -> bool;
}

but... how do you implement such a trait? The only things you could do are compare pointer equality or just infinitely recurse calling other.eq(self).

The way dyn-clone works is that it has a fn clone(&self) -> Box<dyn Trait> method. The equivalent dyn-eq would in fact be fn eq(&self, that: &dyn Trait) -> bool, and implementing such requires the use of Any to downcast.

So it could look something like

trait A: Any {
    fn as_any(&self) -> &dyn Any;
    fn eq(&self, that: &dyn A) -> bool;
}

impl PartialEq for dyn A {
    fn eq(&self, that: &dyn A) -> bool {
        A::eq(self, that)
    }
}

// example
impl<T: Any> A for T {
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn eq(&self, that: &dyn A) -> bool {
        if let Some(that) = that.as_any().downcast_ref::<Self>() {
            self.eq(that)
        } else {
            false
        }
    }
}

... but this doesn't quite work as-is because of something odd with the auto[de]ref rules. (The derive expansion thinks it needs to take the boxes by value for some reason.)

2 Likes

Other notes relating to this comment:

  • dyn Trait automatically implements Trait, but &dyn Trait and Box<dyn Trait>, etc., do not
    • So they're not bound by supertraits, either
    • But sometimes blanket implementations apply, in the case of std traits
  • dyn Trait is dynamically sized, so it can't be taken as Self or returned as Self
    • So it's not possible to implement traits like Clone on dyn Trait directly
  • #[derive(...)] limits its implementations by putting bounds on the types of all the fields
    • So you must have Box<dyn A>: Clone for example
    • Which in turn requires dyn A: Clone
    • Which is impossible
  • dyn Trait is really dyn Trait + 'some_lifetime...
    • And &dyn Trait is short for &'lt (dyn Trait + 'lt)
    • And Box<dyn Trait> is short for Box<dyn Trait + 'static>
    • But Box<dyn Trait + 'lt> and &'lt (dyn Trait + 'static) are also valid
    • So there's flexibility about what flavors of dyn Trait implement what traits, with no unambiguously more-correct variant among the approaches
    • (And some smart pointers not implementing the trait is also perfectly valid!)

And for Box<dyn Trait> specifically

  • Box isn't so special as to be considered the only smart pointer/wrapper
    • E.g. Arc<dyn Trait> exists (and implements Clone...)
  • But it is special enough ("fundamental") to have an exception in the orphan rules:
    • You can implement Trait for Box<X> if you can implement Trait for X
    • On the flip side, Box can never gain another blanket implementation to an existing std trait

This change allows the derive to work in @CAD97's example:

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

impl PartialEq<dyn A> for Box<dyn A + '_> {
    fn eq(&self, that: &dyn A) -> bool {
        A::eq(&**self, that)
    }
}

But it either returns false (for different underlying types) or recurses forever, so it's perhaps not the most useful in the given form.

Basing it on PartialEq<Self> takes things somewhat further, but see the examples for what you lose out on by fixing the type parameter to Self. Dynamically sized types like str can't be coerced to dyn Any either, so depending on the types involved, you may be somewhat stuck without further indirection or allocation.

Pointer equality is another approach, but that's even further away from the PartialEq trait, which is concerned with values.


If the above shortcomings concern you, perhaps consider a supertrait bound on Borrow<_> instead.

Playground.

4 Likes

See Error on deriving PartialEq on Foo and then implementing it for dyn Foo · Issue #78808 · rust-lang/rust · GitHub

1 Like

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.