rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.32k stars 12.58k forks source link

`*x == *y` for trait objects produce move error, so it is not equivalent to `PartialEq::eq(&*x, &*y)` even though the reference says it is #127215

Open estebank opened 3 months ago

estebank commented 3 months ago

The following code produces a move error:

trait Animal {
    fn noise(&self) -> String;
}

impl PartialEq for dyn Animal {
    fn eq(&self, other: &Self) -> bool {
        self.noise() == other.noise()
    }
}

fn f(a1: &Box<dyn Animal>, a2: &Box<dyn Animal>) {
    println!("{}", *a1 == *a2); // doesn't work
}
error[E0507]: cannot move out of `*a2` which is behind a shared reference
  --> src/lib.rs:12:27
   |
12 |     println!("{}", *a1 == *a2); // doesn't work
   |                           ^^^ move occurs because `*a2` has type `Box<dyn Animal>`, which does not implement the `Copy` trait

But according to the reference, it should be equivalent to the following, which does work:

fn f(a1: &Box<dyn Animal>, a2: &Box<dyn Animal>) {
    println!("{}", PartialEq::eq(&*a1, &*a2)); // works
}

It seems to be specific to trait objects; e.g. replacing Box<dyn Animal> with Box<[String]> will make both versions of f compile.

Originally reported in https://github.com/rust-lang/rust/issues/123056#issuecomment-2067785879

eggyal commented 3 months ago

Interestingly it works if the arguments to f are Box<dyn Animal> or &dyn Animal rather than &Box<dyn Animal>.

finnbear commented 1 month ago

Same issue occurs if the container, in this case RcPtrEq, implements PartialEq independently of the dyn trait object:

#![feature(coerce_unsized)]
#![feature(unsize)]

use std::rc::Rc;
use std::marker::Unsize;
use std::ops::{CoerceUnsized};

pub fn main() {
    //#[derive(PartialEq)]
    struct Foo {
        field: RcPtrEq<dyn Fn()>,
    }

    // Doesn't compile.
    #[automatically_derived]
    impl ::core::cmp::PartialEq for Foo {
        #[inline]
        // error[E0507]: cannot move out of `other.field` which is behind a shared reference
        fn eq(&self, other: &Foo) -> bool { self.field == other.field }
    }

    // Works.
    /*
    impl PartialEq for Foo {
        #[inline]
        fn eq(&self, other: &Foo) -> bool { PartialEq::eq(&self.field, &other.field) }
    }
    */
}

pub struct RcPtrEq<T: ?Sized>(Rc<T>);

impl<T: ?Sized + Unsize<U>, U: ?Sized> CoerceUnsized<RcPtrEq<U>> for RcPtrEq<T> {}

impl<T: ?Sized> PartialEq for RcPtrEq<T> {
    fn eq(&self, other: &Self) -> bool {
        Rc::ptr_eq(&self.0, &other.0)
    }
}

Playground link

Strangely, removing CoercedUnized makes it compile.

Artikae commented 3 weeks ago

This also breaks the PartialEq derive macro when used on structs.

use std::rc::Rc;
trait Trait {}
impl PartialEq for dyn Trait {
    fn eq(&self, _: &Self) -> bool { todo!() }
}
#[derive(PartialEq)]
struct User(Rc<dyn Trait>);

Playground link