借用的内容无法移动 - 运算符重载

9

我在使用一个具有运算符重载的类时,从一个简单的帮助方法中得到了编译错误。这里是一个自包含的测试(从我的真实代码简化而来,但仍然演示了问题):

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

我正在尝试使用Rust 1.8编译这个程序,但是遇到了以下错误:

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

我已经阅读了Rust Book,特别是关于所有权和借用的部分。我在StackOverflow上阅读了许多与此相关的问题,例如:Cannot move out of borrowed content。虽然错误相同,但我认为我的问题不是重复的,因为情况不同。如果我知道其他问题如何适用于这个问题,我就不必问了。所以我的问题是:我该如何解决这个错误?我不想将&self更改为self,因为这会引起其他问题。除了修复问题之外,我还想知道Rust害怕什么。我没有看到任何危险。

请参见以下链接:https://dev59.com/hofca4cB1Zd3GeqPqf-I,https://dev59.com/corda4cB1Zd3GeqPJ0CA,http://stackoverflow.com/q/30974593/155423,http://stackoverflow.com/q/28527702/155423,https://dev59.com/hZLea4cB1Zd3GeqP5rbO和https://dev59.com/Fl4b5IYBdhLWcg3wpzPv。 - Shepmaster
1个回答

9
你正在实现 Money 的运算符,而不是 &Money。这意味着该运算符将拥有其操作数的所有权。因此,在 total 中执行加法时,您必须移动 self.absolute,但这是不允许的,因为您不能从借用指针中移动出值(只能移动您拥有所有权的值)。如果类型实现了 Copy(对于像 i32f64 这样的基本类型就是这种情况),Rust 将会复制值;否则,它将会移动它们,这意味着源在移动后将无法使用。
如果你的Money结构体只包含cents字段,我建议你让它实现Copy(这也需要实现Clone,即使你不实现Copy,实现Clone也是一个好主意)。你可以使用#[derive]轻松实现CopyClone
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Money {
    cents: i64,
}

现在,在total中,Rust将复制self.absolute而不是移动它。如果无法实现Copy,则用self.absolute.clone()替换self.absolute
如果您已经在 &Money 上实现了运算符,那么您只需传递到您的 Money 值的引用。例如,使用这样的实现,total 可以像这样实现:
pub fn total(&self, basis: Money) -> Money {
    &self.absolute + &(self.percent * &basis)
}

1
值得一提的是所有权问题,以及 Add 消耗加数的值,甚至可以涉及为什么这样做很有用,以及如果需要,为什么在引用上实现 Add 将允许某人避免实现 Copy - Shepmaster
我认为值得提醒的是 Rust 默认采用“移动语义”(在参数和赋值中都是如此),这对于新手来说通常很陌生,需要进行参考。回答得很好。 - oblitum
非常感谢您提供这个出色的答案。复制/移动/借用正在慢慢地深入人心。这个答案帮了我很多!从教学角度来看,二进制更容易理解,而且据我所知,这本书从来没有将它们呈现为(复制 vs 移动)vs 借用——它似乎总是在对比两者而不提及第三者。把所有三个放在一起非常有帮助。 - Paul A Jungwirth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接