Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
bluss opened this issue Feb 18, 2016 · 16 comments
Open
Labels
A-type-system Area: Type system C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@bluss
Copy link
Member

bluss commented Feb 18, 2016

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;
                          ^
  • This only occurs for the reflexive (x == x) case.
  • It doesn't actually compile into a move in x == y.
  • Fat boxes like Box<[T]> don't behave like this

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
Copy link
Contributor

Aatch commented Feb 18, 2016

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
Copy link
Contributor

durka commented Mar 18, 2017

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
Copy link

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

    assert!(a == b);
    assert!(b == a);

@bluss
Copy link
Member Author

bluss commented Mar 18, 2017

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
Copy link
Member Author

bluss commented Mar 18, 2017

Ah, with durka's explanation it is clear.

@durka
Copy link
Contributor

durka commented Mar 18, 2017

@bluss but it moves y.

@OliverUv
Copy link

@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
Copy link
Member

Triage: no change

@spease
Copy link

spease commented May 9, 2019

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
Copy link

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
Copy link
Member

cuviper commented Aug 7, 2020

#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
Copy link

ryzhyk commented Sep 29, 2020

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
Copy link

ryo33 commented Jan 12, 2023

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:

  • Box<i32> is of course ok
  • x == y takes the ownership of y
  • (here is new!) assert_eq!(x, y) emits a compile error

@Noratrieb Noratrieb added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Apr 5, 2023
@jayzhan211
Copy link

jayzhan211 commented Nov 17, 2023

@eggyal
Copy link
Contributor

eggyal commented Apr 9, 2024

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
Copy link

Jayonas commented Apr 9, 2024

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-type-system Area: Type system C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests