22

Editor's note: This code example is from a version of Rust prior to 1.0 and is not syntactically valid Rust 1.0 code. Updated versions of this code produce different errors, but the answers still contain valuable information.

It seems like we cannot test for equality in the following case. Why is this? Is there a workaround? (I am using 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
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Samuel
  • 517
  • 3
  • 12

3 Answers3

23

With help from Vladimir Matveev, I figured out how to use Any to downcast my trait to a concrete type and test the resulting value for equality:

// `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)
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Samuel
  • 517
  • 3
  • 12
  • No more anonymous parameters. fn equals_a(&self, &A) should use other: &A instead.. See https://doc.rust-lang.org/edition-guide/rust-2018/trait-system/no-anon-params.html – Paul Chernoch Jul 31 '19 at 20:17
18

Here is the definition of the PartialEq trait:

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

    fn ne(&self, other: &Rhs) -> bool { ... }
}

Note the Self parameter type. This means that eq() and ne() methods accept a parameter of the same type as implementor. For example:

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

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

Note how type of other changes to reflect the type PartialEq is implemented for.

This is the problem. In trait objects, the actual type is erased and unavailable at runtime. This means that it is impossible to obtain a reference to a concrete type from a trait object; in particular, you can't go from &A to &T in your example.

This means that it is impossible to call methods accepting or returning the Self type on trait objects. Indeed, these methods always require a concrete type, but if you have only a trait object, there is no concrete type, and there is no way such method could work in any sensible way.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Vladimir Matveev
  • 120,085
  • 34
  • 287
  • 296
  • As a bonus, if you were to try to impl PartialEq for &A as `(*self).eq(*other)`, it complains “cannot call a method whose type contains a self-type through an object [E0038]”. – Chris Morgan Aug 17 '14 at 01:55
  • That makes sense. I guess I would have to dynamically cast first then. Not sure if this is possible in Rust? – Samuel Aug 17 '14 at 16:35
  • @Samuel, no, it is not possible in general; you can't downcast from trait object to concrete type, because it is completely erased. However, there is a trait, [`Any`](http://doc.rust-lang.org/std/any/), which allow downcasting, but it can be used only for `'static` types (ones which do not contain references with non-`'static` lifetimes). Check it out, maybe it can help with your task. – Vladimir Matveev Aug 17 '14 at 17:50
  • @VladimirMatveev, yep, I ended up using Any as you suggested. See my answer. Thanks for the clues! – Samuel Aug 18 '14 at 08:26
17

In certain cases of trait objects, you wish to compare them based on some properties exposed via the trait. You can achieve this by implementing methods on the trait type itself:

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
}

This doesn't directly address the original case which wants to delegate back to the implementation of PartialEq of the underlying type, but you can combine the existing solution:

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

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366