为什么允许在 &mut self 中借用结构成员,但不允许借用 self 到不可变方法?

Why are borrows of struct members allowed in &mut self, but not of self to immutable methods?

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

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() 的参数怎么办?我是否必须按值创建 returns b 的函数并将其存储在绑定中,然后将该绑定传递给 do_something()?如果 b 很复杂怎么办?

根据我的理解,更重要的是,编译器将我从这里拯救了什么样的内存不安全?

可变引用的一个关键方面是当它们存在时保证它们是唯一访问特定值的方式(除非它们被重新借用,"disables"他们暂时)。

写的时候

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 的引用,其中一个是可变的,是非法的。

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

What if I need to do something more complex than just returning a member in order to get the argument for a.do_something()?

我会将 get_bState 移动到 B 并在 self.b 上调用 get_b。这样,编译器可以看到 self.aself.b 上的不同借用,并将接受代码。

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

是的,编译器出于安全检查的目的隔离函数。如果没有,那么每个函数基本上都必须在任何地方内联。至少有两个原因没有人会喜欢这一点:

  1. 编译时间会飙升,许多并行化的机会将不得不放弃。
  2. 对函数 N 调用的更改可能会影响 当前 函数。另请参阅 ,其中涉及相同的概念。

what kind of memory-unsafety is the compiler saving me from here

None,真的。事实上,正如您的示例所示,可以说它正在造成误报。

这对于保持程序员的理智确实更有好处。


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

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

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