rust-lang / rust

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

"cannot move out of" error using `==` with `Box<Trait>`, where no move is needed #31740

Open bluss opened 8 years ago

bluss commented 8 years ago

The following code has a compilation error, but it should be a valid program (playground) (Updated code in 2021 edition)

trait Trait { }
impl<T> Trait for T { }

impl<'a> PartialEq for Trait + 'a {
    fn eq(&self, other: &Self) -> bool { false }
}

fn main() {
    let x = Box::new(1) as Box<Trait>;
    let equal = x == x;
}
<anon>:10:22: 10:23 error: cannot move out of `x` because it is borrowed [E0505]
<anon>:10     let equal = x == x;
                               ^
<anon>:10:17: 10:18 note: borrow of `x` occurs here
<anon>:10     let equal = x == x;
                          ^

The error is the same when using this equality impl instead:

impl<'a> PartialEq for Box<Trait + 'a> {
    fn eq(&self, other: &Self) -> bool { false }
}
Aatch commented 8 years ago

Doing &*x == &*x works. I'm not sure how the compiler gets from &Box<Trait> to &Trait though, so I don't know why it would have issues there.

durka commented 7 years ago

When comparing two different boxes, the equality operation does take ownership of the second one, even though that's plainly impossible from the signature of PartialEq::eq (and it doesn't occur when replacing a == b with PartialEq::eq(&a, &b)): https://is.gd/a3SBnH

OliverUv commented 7 years ago

This does not only occur for the reflexive case. See https://is.gd/MWU7Kw

    assert!(a == b);
    assert!(b == a);
bluss commented 7 years ago

I'm not sure what the difference is. I based that (“This only occurs for the reflexive (x == x) case”) on that this compiles:

trait Trait { }
impl<T> Trait for T { }

impl<'a> PartialEq for Trait + 'a {
    fn eq(&self, other: &Self) -> bool { false }
}

fn main() {
    let x = Box::new(1) as Box<Trait>;
    let y = Box::new(1) as Box<Trait>;
    let equal = x == y;
}
bluss commented 7 years ago

Ah, with durka's explanation it is clear.

durka commented 7 years ago

@bluss but it moves y.

OliverUv commented 7 years ago

@bluss try adding another identical let equal row after, and you'll see the compiler error out. You currently don't get an error because you're not trying to use y after it has been moved and dropped.

steveklabnik commented 5 years ago

Triage: no change

spease commented 5 years ago

Just got this recently.

Had asked on Stack Overflow, vikrrrr and zuurr figured out what was going on at a lower level.

https://stackoverflow.com/questions/56050864/cannot-move-out-of-an-rc

Here's the generated PartialEq for my example:


fn eq(&self, other: &MyEnum) -> bool {
    {
        let __self_vi =
            unsafe { ::std::intrinsics::discriminant_value(&*self) } as
                isize;
        let __arg_1_vi =
            unsafe { ::std::intrinsics::discriminant_value(&*other) } as
                isize;
        if true && __self_vi == __arg_1_vi {
            match (&*self, &*other) {
                (&MyEnum::B(ref __self_0), &MyEnum::B(ref __arg_1_0)) =>
                (*__self_0) == (*__arg_1_0),
                _ => true,
            }
        } else { false }
    }
}```
DerickEddington commented 5 years ago

I encountered this yesterday and was quite confused by it for a while.

I wonder if this issue also occurs with the other comparison operators?

I'd written a new issue report for it today because the issue searcher wasn't finding this issue #31740 for me until I entered a title for my issue, so I may as well add what I boiled down my case to:

trait Mine {}

impl Mine for i32 {}

impl PartialEq for dyn Mine {
    fn eq(&self, _other: &dyn Mine) -> bool {
        true
    }
}

fn main() {
    let b1: Box<dyn Mine> = Box::new(1);
    let b2: Box<dyn Mine> = Box::new(1);
    if b1 == b2 {}
    if b1 == b2 {}
}
error[E0382]: use of moved value: `b2`
  --> src/main.rs:15:14
   |
14 |     if b1 == b2 {}
   |              -- value moved here
15 |     if b1 == b2 {}
   |              ^^ value used here after move
   |
   = note: move occurs because `b2` has type `std::boxed::Box<(dyn Mine + 'static)>`, which does not implement the `Copy` trait

The following alternative form that is supposed to be equivalent (IIUC) works:

    let b1r: &Box<dyn Mine> = &b1;
    let b2r: &Box<dyn Mine> = &b2;
    if <Box<dyn Mine> as PartialEq>::eq(b1r, b2r) {}
    if <Box<dyn Mine> as PartialEq>::eq(b1r, b2r) {}
cuviper commented 4 years ago

75231 has a new example with Rc instead of Box, and this one came from a derived PartialEq.

use std::cell::RefCell;
use std::rc::Rc;

pub trait Trait {}

impl PartialEq<dyn Trait> for dyn Trait {
    fn eq(&self, _other: &Self) -> bool {
        todo!();
    }
}

pub fn eq(a: &Rc<RefCell<dyn Trait>>, b: &Rc<RefCell<dyn Trait>>) -> bool {
    *a == *b
}
ryzhyk commented 3 years ago

I ran into this bug when trying to auto-derive PartialEq for a struct containing field of type Box<dyn Trait>. The workaround that worked for me was to provide an additional PartialEq implementation parameterized by &Self instead of Self:

pub trait Trait {}

/* This impl is not sufficient and causes "the cannot move out of `*__self_1_0` which is behind a shared reference" error.  */
impl PartialEq for Box<dyn Trait> {
    fn eq(&self, _other: &Self) -> bool {
        todo!();
    }
}

/* Code fails to compile without this impl. */
impl PartialEq<&Self> for Box<dyn Trait> {
    fn eq(&self, _other: &&Self) -> bool {
        todo!();
    }
}

#[derive(PartialEq)]
struct Foo {
    x: Box<dyn Trait>
}
ryo33 commented 1 year ago

See this example (also try macro expansion). It's based on @bluss's one.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=e0c3eb71ffb18c747a3e796b2eb11d8f

It gives us the following:

jayzhan211 commented 10 months ago

Not only Box, Arc has the same issue https://users.rust-lang.org/t/introduce-arc-dyn-trait-in-enum-which-impl-partialeq/102649/7

eggyal commented 5 months ago

Here is a repro using a custom type (playground):

#![feature(coerce_unsized, unsize)]
use core::{marker::Unsize, ops::CoerceUnsized};

struct Foo<T: ?Sized>(*const T);

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

impl<T: ?Sized + PartialEq> PartialEq for Foo<T> {
    fn eq(&self, _: &Self) -> bool { loop {} }
}

trait Trait {}

impl PartialEq for dyn Trait {
    fn eq(&self, _: &Self) -> bool { loop {} }
}

fn test(x: Foo<dyn Trait>, y: Foo<dyn Trait>) {
    x == y;
    drop(y);
}

Note line 6, the implementation of CoerceUnsized for our custom type, without which it works okay. So it would appear to be down to some erroneous interaction between unsized coercions and the comparison binary operators (it's not just equality, all comparisons fail similarly).

The strange thing is that no unsized coercion is necessary here (albeit they must probably be used elsewhere in order to obtain instances of Foo<dyn Trait>).

Interestingly, if the ?Sized bound is removed from T in the CoerceUnsized implementation, one gets the following error:

error[E0277]: the size for values of type `(dyn Trait + 'static)` cannot be known at compilation time
  --> src/lib.rs:19:10
   |
19 |     x == y;
   |          ^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `(dyn Trait + 'static)`
   = note: required for the cast from `Foo<(dyn Trait + 'static)>` to `Foo<dyn Trait>`

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

So it looks like the comparison operators are attempting to coerce from one unsized type (with 'static bound) to another (without, on which the PartialEq impl is defined).

Jayonas commented 5 months ago

I ran into this bug the other day but failed to discover this thread while trying to figure it out, so I ended up asking about it on StackOverflow. Now that I know about the bug and have already created a repro and SO question, I figured that I should add it to the set of examples: Why does the equality operator for owned trait object move the right operand?