为什么在& mut self中允许借用struct成员,而不是self到不可变方法?

时间:2017-03-18 19:50:45

标签: rust borrow-checker memory-safety

如果我有一个封装两个成员的结构,并根据另一个更新一个,只要我这样做就可以了:

struct A {
    value: i64
}

impl A {
    pub fn new() -> Self {
        A { value: 0 }
    }
    pub fn do_something(&mut self, other: &B) {
        self.value += other.value;
    }
    pub fn value(&self) -> i64 {
        self.value
    }
}

struct B {
    pub value: i64
}

struct State {
    a: A,
    b: B
}

impl State {
    pub fn new() -> Self {
        State {
            a: A::new(),
            b: B { value: 1 }
        }
    }
    pub fn do_stuff(&mut self) -> i64 {
        self.a.do_something(&self.b);
        self.a.value()
    }
    pub fn get_b(&self) -> &B {
        &self.b
    }
}

fn main() {
    let mut state = State::new();
    println!("{}", state.do_stuff());
}

也就是说,当我直接引用self.b时。但是当我将do_stuff()改为此时:

pub fn do_stuff(&mut self) -> i64 {
    self.a.do_something(self.get_b());
    self.a.value()
}

编译器抱怨:cannot borrow `*self` as immutable because `self.a` is also borrowed as mutable

如果我需要做一些比仅返回一个成员更复杂的事情以获得a.do_something()的参数怎么办?我必须创建一个按值返回b并将其存储在绑定中的函数,然后将该绑定传递给do_something()吗?如果b很复杂怎么办?

更重要的是,根据我的理解,编译器将我从这里拯救出来的是什么样的记忆 - 不安全?

2 个答案:

答案 0 :(得分:7)

可变引用的一个关键方面是它们保证在存在时访问特定值的唯一方式(除非它们被重新借用,暂时“禁用”它们。)< / p>

写作时

self.a.do_something(&self.b);

编译器能够看到self.a上的借用(隐含地执行方法调用)与self.b上的借用不同,因为它可以推断直接字段访问。

但是,当你写

self.a.do_something(self.get_b());

然后编译器看不到self.b上的借位,而是self上的借位。这是因为方法签名上的生命周期参数无法传播有关借用的详细信息。因此,编译器无法保证self.get_b()返回的值不允许您访问self.a,这将创建两个可以访问self.a的引用,其中一个是可变的,是非法的。

字段借用不跨函数传播的原因是简化类型检查和借用检查(对于人类的机器)。原则是签名应该足以执行这些任务:更改函数的实现不应该导致其调用者出错。

  

如果我需要做一些更复杂的事情,而不仅仅是为了获得a.do_something()的参数而返回一个成员会怎样?

我会将get_bState移至B并在get_b上致电self.b。这样,编译器可以在self.aself.b上看到明确的借用,并接受代码。

self.a.do_something(self.b.get_b());

答案 1 :(得分:1)

是的,编译器为了进行安全检查而隔离了函数。如果没有,那么每个函数都必须在任何地方内联。没有人会因为至少两个原因而欣赏这一点:

  1. 编译时间将通过屋顶,并且必须放弃许多并行化的机会。
  2. 对函数的更改N调用可能会影响当前函数。另请参阅Why are explicit lifetimes needed in Rust?,其中涉及相同的概念。
  3.   

    什么样的记忆 - 不安全是编译器从这里拯救我

    没有,真的。事实上,正如你的例子所示,可能有人认为它会产生误报。

    这对于保持程序员理智更有利。

    当我遇到这个问题时,我给出并遵循的一般建议是编译器指导您在现有代码中发现新类型。

    您的特定示例有点过于简单,但是如果您有struct Foo(A, B, C)并且发现Foo上的方法需要AB,这通常是一个好的迹象,表明存在由AB组成的隐藏类型:struct Foo(Bar, C); struct Bar(A, B)

    这不是一个灵丹妙药,因为你最终可能会得到需要每对数据的方法,但根据我的经验,它在大多数情况下都有效。