Search code examples
rustoperator-overloadingownershipborrowing

Ownership of values passed to overloaded PartialEq


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

Solution

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