因为`*self`被可变地借用,所以无法将`self.x`作为不可变的借用。

17

首先,让代码说话:

#[derive(Debug)]
struct Bar;

#[derive(Debug)]
struct Qux {
    baz: bool
}

#[derive(Debug)]
struct Foo {
    bars: Vec<Bar>,
    qux: Qux,
}

impl Foo {
    fn get_qux(&mut self) -> &mut Qux {
        &mut self.qux
    }

    fn run(&mut self) {
        // 1. Fails:
        let mut qux = self.get_qux();

        // 2. Works:
        // let mut qux = &mut Qux { baz: false };

        // 3. Works:
        // let mut qux = &mut self.qux;

        let qux_mut = &mut qux;
        qux_mut.baz = true;

        for bar in &self.bars {
            println!("{:?}", bar);
        }
    }
}

fn main() {
    println!("Hello, world!");

    let mut foo = Foo { bars: vec!(), qux: Qux { baz: false } };
    foo.run();
}

这是错误:

error[E0502]: cannot borrow `self.bars` as immutable because `*self` is also borrowed as mutable
  --> src/main.rs:33:21
   |
22 |         let mut qux = self.get_qux();
   |                       ---- mutable borrow occurs here
...
33 |         for bar in &self.bars {
   |                     ^^^^^^^^^ immutable borrow occurs here
...
36 |     }
   |     - mutable borrow ends here

如果我取消注释2.3.中的任意一个,为什么它可以编译?1.中的被调用函数与2.3.并没有太大的区别。那么为什么1.不能编译呢?
虽然有许多类似标题的问题,但我无法明确将其识别为重复项(除了错误消息相同之外),可能是因为我不理解Rust中的所有权/借用系统。
1个回答

12

Rust编译器在评估通用参数时,包括通用生命周期参数,会在函数调用边界停止。在您的情况1中,您正在调用一个方法:

fn get_qux(&mut self) -> &mut Qux {
    &mut self.qux
}

这个函数表明self的所有权将被可变地借用,并且返回的引用将与self的生命周期相同。在此期间,不能对self或其组件进行任何其他(可变或不可变)的借用。

在第二种情况下,你创建了一个完全不与你的结构体相关联的新Qux。这不是一个很好的例子,因为它的意义非常不同。 如果这种情况适合你,那么你应该这样做。然而,你不会修改和第一种情况中的相同内容。

在第三种情况下,你避免了函数调用。这意味着编译器有更多关于借用的确切信息。具体来说,它可以看到self.quxself.bars没有任何交互,因此不会出现错误。

你可以通过添加新的作用域使原始示例工作:

fn run(&mut self) {
    {
        let mut qux = self.get_qux();
        let qux_mut = &mut qux;
        qux_mut.baz = true;
    }

    for bar in &self.bars {
        println!("{:?}", bar);
    }
}

在这里,人工作用域清晰地定义了可变借用的结束位置。一旦借用结束,其他项目就可以进行新的借用。

如果您需要在循环内修改 qux,那么您需要遵循第三种模式:

let mut qux = &mut self.qux;

for bar in &self.bars {
    qux.baz = ! qux.baz;
    println!("{:?}", bar);
}

或者更简单的:

for bar in &self.bars {
    self.qux.baz = ! self.qux.baz;
    println!("{:?}", bar);
}

很多时候,你可以重构你的代码以创建新的结构体,其中包含信息并封装了一个良好的变异边界,以使得像这样的代码更加简洁。


1
我明白了。但是假设该函数有一些额外的逻辑,例如它接受一个id并从Quxes的vec中检索qux,那么我需要在每个地方都添加该逻辑。也许我需要使用宏?...我知道HashMapVec类型有一个get_mut方法,也许可以从它们的实现中学到一些东西。我将不得不再深入研究一下。 - Felix Schlitter
1
@FelixSchlitter 如果没有具体的示例(可能需要另一个问题),很难给出任何可行的建议。然而,将具有共享数据和方法的结构重构或接受闭包都是我解决这种问题的有用方法。 - Shepmaster

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