I have been playing around with overloading some operators. I ran into a situation that I don't quite understand. When implementing the trait PartialEq
for my struct Value
, I noticed that the implementation below works and doesn't move the values, allowing me to continue to use the values after using the ==
operator on them without passing references of the values into the operator.
On the other hand, this doesn't work for the implementation of the Neg
trait (or Add
, Sub
, etc.). In order to use the -
operator without moving the value, I have to implement the Neg
trait on references to the Value
struct.
Why can I implement the PartialEq
trait without having to worry about a move when not passing in a reference to the values, but when implementing the Neg
trait I do need to worry? Am I implementing the Neg
trait incorrectly? Is there a subtlety to the PartialEq
trait that I am overlooking?
Here is my code:
struct Value {
x: i32
}
impl PartialEq for Value {
fn eq(&self, other: &Value) -> bool {
if self.x == other.x {
true
} else {
false
}
}
}
impl Eq for Value {}
impl Neg for &Value {
type Output = Value;
fn neg(self) -> Self::Output {
Value {
x: -self.x
}
}
}
fn main() {
let v1: Value = Value {x: 1};
let v2: Value = Value {x: 2};
let equal = v1 == v2; // Not passing a reference, but also able to use v1
let v3 = -&v1;
let v4 = -&v1; // Works because I am passing a reference. If I change the implementation of Neg to 'impl Neg for Value' and remove the reference here and in the line above (for v3), it will complain that v1 had been moved (as expected).
}
Is there a subtlety to the
PartialEq
trait that I am overlooking?
PartialEq
's methods take self
and other
by reference (&self
and other: &T
in the signature), while Neg
, Add
, Sub
, etc. take self
and (for binary operators) other
by value (self
and other: T
in the signature). v1 == v2
desugars to PartialEq::eq(&v1, &v2)
, while !v1
desugars to Neg::neg(v1)
.
The reason why you might want Neg
to take ownership of the passed value is if the value has allocated dynamic memory (via Box
, Vec
, etc.). In that case, it might be more efficient to mutate self
and then return self
(or another object reusing the dynamic memory in the case where the Output
type is different from the Self
type) instead of allocating a new object (which would require new dynamic memory allocations), even if the original value is not used after the operation.
On the other hand, PartialEq
's methods always return a bool
. A bool
doesn't allocate any dynamic memory, therefore there is no gain in passing the parameters by value. It's not expected that testing whether two objects are equal would need to mutate either or both of the objects, hence why the parameters are shared references.
Am I implementing the
Neg
trait incorrectly?
No, but you might want to consider implementing Neg
for both Value
and &Value
(especially if you're writing a library for others to use).
If your type is cheap to copy (i.e. it's small and doesn't use dynamic memory), consider implementing Clone
and Copy
(possibly by deriving them) as well. This way, you can pass values to the operators without moving the value to the operator, because the value will be copied instead.