8

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

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复杂怎么办?

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

4

2 回答 2

9

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

当你写

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

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

然而,当你写

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

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

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

如果我需要做一些比返回成员更复杂的事情来获取参数a.do_something()怎么办?

我会get_bStateB打电话get_bself.b这样,编译器可以看到不同的借用,self.a并将self.b接受代码。

self.a.do_something(self.b.get_b());
于 2017-03-18T20:26:51.053 回答
2

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

  1. 编译时间会过长,许多并行化的机会将不得不放弃。
  2. 对函数 N 调用的更改可能会影响当前函数。另请参阅为什么 Rust 需要显式生命周期?这涉及相同的概念。

编译器从这里救了我什么样的内存不安全

没有,真的。实际上,正如您的示例所示,可以说它正在制造误报。

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


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

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

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

于 2017-03-18T20:33:13.667 回答