Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"cannot move out of borrowed content" with operator overloading

Tags:

rust

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.

like image 965
Paul A Jungwirth Avatar asked May 19 '16 03:05

Paul A Jungwirth


1 Answers

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)
}
like image 103
Francis Gagné Avatar answered Nov 12 '22 23:11

Francis Gagné