9

I'm getting a compilation error from a simple helper method, when using a class with operator overloading. Here is a self-contained test (simplified from my real code, but still demonstrating the problem):

use std::ops::{Add, Sub, Neg, Mul, Div};

#[derive(Debug, Eq, PartialEq)]
pub struct Money {
    cents: i64,
}
impl Money {
    pub fn new(cents: i64) -> Money {
        Money { cents: cents }
    }
}
impl Add for Money {
    type Output = Money;
    fn add(self, other: Money) -> Money {
        Money { cents: self.cents + other.cents }
    }
}
impl Mul<Money> for f64 {
    type Output = Money;
    fn mul(self, rhs: Money) -> Money {
        Money { cents: (self * rhs.cents as f64) as i64 }
    }
}

#[derive(Debug)]
pub struct AbsOrPerc {
    pub absolute: Money,
    pub percent: f64,
}
impl AbsOrPerc {
    pub fn new(abs: Money, perc: f64) -> AbsOrPerc {
        AbsOrPerc {
            absolute: abs,
            percent: perc,
        }
    }

    pub fn total(&self, basis: Money) -> Money {
        // This works:
        // Money::new((self.absolute.cents as f64 + self.percent * basis.cents as f64) as i64)
        // This doesn't:
        self.absolute + self.percent * basis
    }
}

I'm trying to compile this with Rust 1.8, but I'm getting this error:

src/lib.rs:42:5: 42:9 error: cannot move out of borrowed content [E0507]
src/lib.rs:42     self.absolute + self.percent * basis

I've read the Rust Book, and the parts about ownership and borrowing over and over. I've read numerous questions here on StackOverflow about this question, e.g.:

Cannot move out of borrowed content

I don't think my own question is a duplicate because while the error is the same, the circumstances are different. Also if I knew how those other questions applied to this one, I wouldn't have to ask. :-)

So my question is: how can I resolve this error? I don't want to change &self to self, because that causes other problems.

Besides just fixing the problem, I would also like to know what Rust is scared of. I don't see any dangers here.

Community
  • 1
  • 1
Paul A Jungwirth
  • 23,504
  • 14
  • 74
  • 93
  • See also http://stackoverflow.com/q/28595075/155423, http://stackoverflow.com/q/29926724/155423, http://stackoverflow.com/q/30974593/155423, http://stackoverflow.com/q/28527702/155423, http://stackoverflow.com/q/34621969/155423, and http://stackoverflow.com/q/28843931/155423. – Shepmaster May 19 '16 at 03:26

1 Answers1

9

You're implementing the operators on Money rather than &Money. This means that the operator will take ownership of its operands. Therefore, in total, to perform the addition, you'd have to move self.absolute, which isn't allowed because you can't move out of a borrowed pointer (you can only move values you have ownership of). Rust will copy values if their type implements Copy (which is the case for primitives like i32 or f64); otherwise, it will move them, which means that the source will be unusable after the move.

If your Money struct really only contains a cents field, I recommend you make it implement Copy (which also requires implementing Clone, which would be a good idea to implement even if you don't implement Copy). You can implement Copy and Clone easily with #[derive]:

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Money {
    cents: i64,
}

Now, in total, instead of moving self.absolute, Rust will copy it instead. If you can't implement Copy, then replace self.absolute with self.absolute.clone().


If you had implemented the operators on &Money, then you could just pass references to your Money values. For example, with such implementations, total could be implemented like this:

pub fn total(&self, basis: Money) -> Money {
    &self.absolute + &(self.percent * &basis)
}
Francis Gagné
  • 60,274
  • 7
  • 180
  • 155
  • 1
    It may be worth commenting on ownership and the fact that `Add` consumes the addends by value, maybe even touching on why such a thing is useful and why implementing `Add` on a reference would allow someone to avoid implementing `Copy` if they needed to. – Shepmaster May 19 '16 at 03:22
  • I think it deserves to recall Rust is _move by default_ (in args just like in assignments), which is generally alien to newcomers, with a reference. Good answer. – oblitum May 19 '16 at 03:23
  • Thank you for this great answer. Copy/move/borrow is slowly sinking in. This answer helps a lot! Also pedagogically, binaries are easier to understand, and the Book never to my knowledge really presents them as (Copy vs Move) vs Borrow---it always seems to contrast two and not mention the third. Putting all three together is very helpful. – Paul A Jungwirth May 19 '16 at 03:56