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

63ea68a
Opened by bluss at 2024-12-31 21:09:45

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

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 }
}
  1. 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 }
    }
    

    Dylan DPC at 2023-03-09 15:07:33

  2. 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.

    James Miller at 2016-02-18 00:36:29

  3. 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

    Alex Burka at 2017-03-18 20:10:02

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

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

    Oliver Uvman at 2017-03-18 20:19:21

  5. 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 at 2017-03-18 20:30:48

  6. Ah, with durka's explanation it is clear.

    bluss at 2017-03-18 20:31:31

  7. @bluss but it moves y.

    Alex Burka at 2017-03-18 20:31:40

  8. @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.

    Oliver Uvman at 2017-03-18 20:40:54

  9. Triage: no change

    Steve Klabnik at 2018-10-31 19:46:43

  10. 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 }
        }
    }```
    

    spease at 2019-05-09 01:26:00

  11. 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) {}
    

    Derick Eddington at 2019-05-09 19:59:07

  12. #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
    }
    

    Josh Stone at 2020-08-07 15:29:47

  13. 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>
    }
    

    Leonid Ryzhyk at 2020-09-29 19:53:31

  14. 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

    Ryo Hirayama at 2023-01-12 06:52:06

  15. 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

    Jay Zhan at 2023-11-17 00:59:04

  16. 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).

    eggyal at 2024-04-09 12:38:50

  17. 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?

    Jayonas at 2024-04-09 14:37:34

  18. It seems like == can coerce the right argument. Which is mostly relevant for unsizing coercions (but not exclusively). And unfortunately it coerces by value. <small>[E.g. Deref-coercions don't really come into play, because &mut _ and &_ already have so many different PartialEq implementations, so that (at least typically), coercions are removed by the compiler before they could ever materialize, in order to avoid ambiguous types.]</small>

    I think any fix to improve the situation would at a minimum have to break code like this:

    use std::cell::Cell;
    
    trait Trait {}
    
    impl PartialEq for dyn Trait + '_ {
        fn eq(&self, _: &dyn Trait) -> bool {
            true
        }
    }
    
    // not the same lifetimes, only one direction of outlives: 'b: 'a
    fn f<'a, 'b: 'a>(x: Cell<&'a (dyn Trait + 'a)>, y: Cell<&'b (dyn Trait + 'b)>) {
        x == y;
    }
    

    because even if we added a fix to "avoid the coercion if the type wouldn't change", such a check would realistically not be able to consider lifetimes.

    Additionally, it would of course affect some drop orders, though realistically, those were super surprising anyways. (As it drops the right argument early, but not the left).

    Here's a case with slices which stops working with a PartialEq::eq(…)-desugaring either

    fn f(x: Box<[i32]>, y: Box<[i32; 3]>) {
        x == y; // works (but consumes `y` by value)
    }
    

    (nice that it works, but also kind-of confusing, especially regarding the drop-order)

    Here's a case with reference to pointer coercion:

    fn f(x: *const u8, y: &mut u8) {
        x == y;
        // PartialEq::eq(&x, &y); doesn't work
    }
    

    though it's less bad because this still doesn't consume y (it produces &raw const *y in the MIR).


    IMHO, long-term it could be reasonable to fully replace == with the PartialEq::eq(&left, &right) desugaring that the reference promises, at least over an edition, assuming the broken cases can probably all be fixed by rewriting left == right into left == (right as _), which is easily machine-applicable.

    Frank Steffahn at 2024-12-31 21:08:47