"cannot move out of" error using == with Box<Trait>, where no move is needed
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 }
}
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
Doing
&*x == &*xworks. I'm not sure how the compiler gets from&Box<Trait>to&Traitthough, so I don't know why it would have issues there.James Miller at 2016-02-18 00:36:29
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 replacinga == bwithPartialEq::eq(&a, &b)): https://is.gd/a3SBnHAlex Burka at 2017-03-18 20:10:02
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
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
Ah, with durka's explanation it is clear.
bluss at 2017-03-18 20:31:31
@bluss but it moves
y.Alex Burka at 2017-03-18 20:31:40
@bluss try adding another identical
let equalrow after, and you'll see the compiler error out. You currently don't get an error because you're not trying to useyafter it has been moved and dropped.Oliver Uvman at 2017-03-18 20:40:54
Triage: no change
Steve Klabnik at 2018-10-31 19:46:43
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
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` traitThe 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
#75231 has a new example with
Rcinstead ofBox, and this one came from a derivedPartialEq.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
I ran into this bug when trying to auto-derive
PartialEqfor astructcontaining field of typeBox<dyn Trait>. The workaround that worked for me was to provide an additionalPartialEqimplementation parameterized by&Selfinstead ofSelf: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
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 okx == ytakes the ownership ofy- (here is new!)
assert_eq!(x, y)emits a compile error
Ryo Hirayama at 2023-01-12 06:52:06
Not only
Box,Archas the same issue https://users.rust-lang.org/t/introduce-arc-dyn-trait-in-enum-which-impl-partialeq/102649/7Jay Zhan at 2023-11-17 00:59:04
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
CoerceUnsizedfor 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
?Sizedbound is removed fromTin theCoerceUnsizedimplementation, 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 errorSo it looks like the comparison operators are attempting to coerce from one unsized type (with
'staticbound) to another (without, on which thePartialEqimpl is defined).eggyal at 2024-04-09 12:38:50
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
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 differentPartialEqimplementations, 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 eitherfn 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 *yin the MIR).
IMHO, long-term it could be reasonable to fully replace
==with thePartialEq::eq(&left, &right)desugaring that the reference promises, at least over an edition, assuming the broken cases can probably all be fixed by rewritingleft == rightintoleft == (right as _), which is easily machine-applicable.Frank Steffahn at 2024-12-31 21:08:47