4

Is it possible to implement PartialEq for a trait reference that returns true for implementations instances that have the same inner content? The use case is the following snippet:

trait Trait: ::std::fmt::Debug {
    fn method(&self, i32) -> ();
}

#[derive(Debug, PartialEq)]
struct Struct<'a> {
    vec: Vec<&'a Trait>,
}

#[derive(Clone, Copy, Debug, PartialEq)]
struct AnotherStruct {
   int: i32
}

impl From<AnotherStruct> for i32 {
    fn from(another_struct: AnotherStruct) -> i32 {
        another_struct.int
    }
}

impl<T> Trait for T
where
    T: Copy + ::std::convert::Into<i32> + ::std::fmt::Debug
{
    fn method(&self, value: i32) -> () {
        let into: i32 = (*self).into();
        println!("{} * {} = {}", into, value, into * value);
    }
}

fn main() {
    let a = Struct { vec: vec![&AnotherStruct { int: 5 }, &10i8] };
    let b = Struct { vec: vec![&AnotherStruct { int: 5 }, &10i8] };
    let c = Struct {
        vec: vec![
            &AnotherStruct { int: 3 },
            &3i16,
            &3u16,
            &AnotherStruct { int: 3 }
        ]
    };
    assert_eq!(a, b);
    assert_eq!(c.vec[0], c.vec[3]);
    c.vec[1].method(5);
}

Can you see in the final lines that I trying to compare two instances of AnotherStruct that have the same int value? Unfortunately, this code doesn't compile (No PartialEq for &Trait) and since Trait isn't a real object, it isn't possible to store traits references inside a vector using trait Trait: PartialEq or trait Trait<'a>: PartialEq<&'a Self>. Note: Vec<Box<Trait>> isn't a viable option because of the runtime cost of memory allocation.

Knowing this limitations and also knowing that the method returning type can't be used as a unique discriminator, I am sharing some attempts I made in hope to shed some light for someone willing to help.

  1. Use the method function pointer like Rust did for fn pointers to implement PartialEq. After talking with two nice people on IRC about this strategy, they basically told me that I could try casting using as *const _ but it isn't reliable because of possible code deduplication.

    // Compiler error: attempted to take value of method `method` on type `&&'a Trait + 'a`
    
    trait Trait {
        fn method(&self, i32) -> ();
    }
    
    impl<'a> PartialEq for &'a Trait {
        fn eq(&self, other: &Self) -> bool {
            *self.method as *const _ == *other.method as *const _
        }
    }
    
    #[derive(PartialEq)]
    struct Struct<'a> {
        vec: Vec<&'a Trait>,
    }
    
  2. Return the method as a function from another method and use it as a discriminator. This approach works but isn't very ergonomic and I don't consider it a solution.

    trait Trait {
        fn another_method(&self) -> fn(i32);
    }
    
    impl<'a> PartialEq for &'a Trait {
        fn eq(&self, other: &Self) -> bool {
            self.another_method() == other.another_method()
        }
    }
    
    #[derive(PartialEq)]
    struct Struct<'a> {
        vec: Vec<&'a Trait>,
    }
    
Caio
  • 3,178
  • 6
  • 37
  • 52
  • @ljedrz It may not be _exactly_ the same. With `PartialEq`, it's quite likely that you'll want to use members of the concrete type, which means that you won't be able to implement it for the trait at all. More likely, what the OP tried (along the lines of `trait Trait: PartialEq`) is the right approach, but he didn't explain what problem he actually had with that... – Peter Hall Apr 27 '18 at 13:08
  • @PeterHall fair enough. – ljedrz Apr 27 '18 at 13:10
  • @PeterHall I will try to improve the question – Caio Apr 27 '18 at 13:27
  • 1
    Why is `*self as *const _ == *other as *const _` (just comparing the trait object references) not sufficient? – trent Apr 28 '18 at 16:56
  • @trentcl If the trait has several methods, can this approach be used reliably? – Caio Apr 29 '18 at 14:05
  • 1
    Please [edit] your question to explain why it's not a duplicate of [How to test for equality between trait objects?](https://stackoverflow.com/q/25339603/155423) – Shepmaster Apr 29 '18 at 15:04
  • 1
    *This approach isn't reliable [...] and didn't work using either as `usize` or as `*const _`* — what do you mean by "reliable"? What do you mean by "didn't work"? These are crucial bits of information we need to avoid wasting our own time investigating avenues. – Shepmaster Apr 29 '18 at 15:10
  • 1
    @Caio I guess I don't know what effect you're trying to achieve, so I don't know what you mean by "reliably". It will give `false` when the two references are to distinct objects of the same type, so maybe it's not what you want. But it can never give `true` when the objects are of different type. It behaves reliably in that sense. – trent Apr 29 '18 at 17:01
  • 2
    @Caio I'm fairly confident that this question, in its current form, in an [XY Problem](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem). It seems likely that you don't actually care about testing if that specific method is the same, but that you've arrived there after previous work. My guess is that you actually want to perform a comparison based on if the two concrete types are the same, or something else a few steps back. If you [edit] your question to focus on the problem you are trying to solve, it's more likely you will get an answer. – Shepmaster Apr 29 '18 at 18:53
  • It seems like you're basically trying to reinvent the https://doc.rust-lang.org/std/any/index.html Any part of the standard library. What you want is something like `if a.get_type_id() == b.get_type_id() { a.downcast_ref() == b.downcast_ref() }` – Linear May 01 '18 at 08:20
  • But I agree this seems like more of an X Y problem – Linear May 01 '18 at 08:23
  • trentcl, Shepmaster: The security concern was a IRC suggestion that worried me. Someone who I forgot the name told me that the compiler or the linker could erase/deduplicate the memory location of the pointer used for the PartialEq comparation in a optimization pass – Caio May 01 '18 at 10:47
  • @Shepmaster Sorry for the possible misunderstanding, I will edit the question accordingly – Caio May 01 '18 at 11:07
  • @trentcl Your `*self as *const _ == *other as *const _` suggestion solves my problem and you might want to answer to receive the bounty – Caio May 01 '18 at 15:45
  • Actually, I don't think it does. Compare [this version](https://play.rust-lang.org/?gist=2284e931085e0ad932c809ed9aaad1d2&version=stable&mode=debug) (the `main` function is the only thing I changed). If that's not what you want, it'll take some more fancy techniques. – trent May 01 '18 at 15:58
  • @trentcl You are right, `*self as *const _ == *other as *const _` is only true for the same instance. I guess that what I am asking is just impossible because a discriminator method is needed to implement `PartialEq`. – Caio May 01 '18 at 17:02
  • 1
    "for implementations instances that have the same inner content" — That sounds exactly like the problem addressed in [How to test for equality between trait objects?](https://stackoverflow.com/q/25339603/155423). In that case, it uses `PartialEq` to provide the method that returns the boolean, but the idea is the same. **Please** [edit] your question to explain how it's not the same. – Shepmaster May 01 '18 at 17:24
  • 1
    @Shepmaster It seems like it is the same thing. You can mark this question as duplicated or I can close it myself. – Caio May 01 '18 at 17:44
  • @Caio if you would do it, it's probably better. I get a lot of... anger... from people seeing I have closed duplicates. Having the OP's name on there helps a lot ;-) – Shepmaster May 01 '18 at 17:45
  • 1
    @Shepmaster Deal. I just need to contact some moderator or wait the bounty period. – Caio May 01 '18 at 17:59

0 Answers0