Search code examples
stringrustborrow-checkerownership

Return ownership from an overloaded operator if Copy trait is unavailable


With a struct that contains a String (thus preventing implementation of Copy trait), if I use the struct with an overloaded operator, I am unable to use the struct variable again (which is expected). However, I am unable to figure out how to implement my own Copy trait or work around this in some other way. I am not modifying the string in the operator at all.

How do I return the ownership of a struct from an operator such that the struct can be used again?

Minimal Case:

If I try to compile the following code:

use std::ops;

struct Value {
    val: i32,
    name: String
}

impl std::fmt::Display for Value {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}({})", self.name, self.val)
    }
}

impl ops::Add<Value> for Value {
    type Output = Value;

    fn add(self, _rhs: Value) -> Value {
        Value { val: self.val + _rhs.val, 
                name: format!("({}+{})", self.name, _rhs.name) }
    }
}

fn main() {
    let a = Value {val: 5, name: "a".to_string()};
    let b = Value {val: 6, name: "b".to_string()};
    
    let mut c = a + b;
    
    c = c + a;
    
    println!("{}", c);
}

I get the following error:

error[E0382]: use of moved value: `a`
  --> src/main.rs:29:13
   |
24 |     let a = Value {val: 5, name: "a".to_string()};
   |         - move occurs because `a` has type `Value`, which does not implement the `Copy` trait
...
27 |     let mut c = a + b;
   |                 ----- `a` moved due to usage in operator
28 |     
29 |     c = c + a;
   |             ^ value used here after move
   |
note: calling this operator moves the left-hand side

Solution

  • Add can be implemented for references to types too. So for example you could write something like this:

    impl ops::Add<&Value> for &Value {
        type Output = Value;
    
        fn add(self, _rhs: &Value) -> Value {
            Value { val: self.val + _rhs.val, 
                    name: format!("({}+{})", self.name, _rhs.name) }
        }
    }
    

    and use it like this:

    fn main() {
        let a = Value {val: 5, name: "a".to_string()};
        let b = Value {val: 6, name: "b".to_string()};
        
        let c = &a + &b;
        println!("{}", c);
        let c = &c + &a;
        println!("{}", c);
    }
    

    You can also implement Add<&Value> for Value (or vice versa) which will consume one operand. For example in standard library there is impl Add<&str> for String. You have to choose which semantics you want (do you want to take ownership of both operands, only one, or neither).

    However I would suggest to just use normal methods as it won't result in surprises when someone tries to use Value. As a rule of thumb try to "override" operators only when there is only one logical implementation.