Comparison using `!=` vs calling `ne` directly

I have the following code that fails to compile:

trait A : {
    fn as_u64(&self) -> u64;
}

impl A for u64 {
    fn as_u64(&self) -> u64 {
        *self
    }
}

impl PartialEq for Box<dyn A> {
    fn eq(&self, other: &Self) -> bool {
        self.as_u64() == other.as_u64()
    }
}

fn main() {
    let p1: Box<dyn A> = Box::new(21);
    let p2: Box<dyn A> = Box::new(22);
    let p3: Box<dyn A> = Box::new(21);
    assert!(p1 != p2);
    assert!(p3 != p2);
    assert!(p1 == p3);
}

Error produced:

   Compiling playground v0.0.1 (/playground)
error[E0382]: use of moved value: `p2`
  --> src/main.rs:22:19
   |
19 |     let p2: Box<dyn A> = Box::new(22);
   |         -- move occurs because `p2` has type `Box<dyn A>`, which does not implement the `Copy` trait
20 |     let p3: Box<dyn A> = Box::new(21);
21 |     assert!(p1 != p2);
   |                   -- value moved here
22 |     assert!(p3 != p2);
   |                   ^^ value used here after move

For more information about this error, try `rustc --explain E0382`.
error: could not compile `playground` (bin "playground") due to 1 previous error

But if I change assert!(p1 != p2) to assert!(p1.ne(&p2));, it compiles and runs fine, without errors. assert!(&p1 != &p2); works fine too, but clippy throws a message asking me to use assert!(p1 != p2) which doesn't compile.

From what I understand, != should be synonymous with using ne, so am I missing something here?

1 Like

Interesting. It seems you have found a compiler/language bug. Normally, the == and != operators implicitly borrow their arguments, but in the particular case of Box<dyn Trait> something else happens. This seems to be the relevant issue:

11 Likes

Thank you. I'll just workaround by calling eq / ne directly.

Two notes:

  1. There is a workaround:
    impl PartialEq<&Box<dyn A + '_>> for Box<dyn A + '_> {
        fn eq(&self, other: &&Box<dyn A + '_>) -> bool {
           self.as_u64() == other.as_u64()
        }
    }
    
  2. Your existing implementation only supports Box<dyn A + 'static> and can be made more general:
    impl PartialEq<Box<dyn A + '_>> for Box<dyn A + '_> {
        fn eq(&self, other: &Box<dyn A + '_>) -> bool {
            self.as_u64() == other.as_u64()
        }
    }
    

Why do you add an explicit type? Inferring works just fine:

    let p1 = Box::new(21);
    let p2 = Box::new(22);
    let p3 = Box::new(21);

I guess in real code there is Box<dyn Trait> coming from somewhere else, where it has to be a trait object. In your code, every variable will be Box<{integer}>, not Box<dyn A>.

I think you are right, so it looks like a clear compiler bug which exists in the night build version.

Funny thing that people do not understand that safety != logically correct. For example, when I did a testing of the example and slightly modified the implementation to be sure that a correct function called:

impl PartialEq for Box<dyn A> {
    fn eq(&self, other: &Self) -> bool {
        (self.as_u64() + 1) == other.as_u64()
    }
}

The following operations became correct:

assert!(&p1 == &p2);
assert!(&p2 != &p1);
assert!(p1 != p3);