Rust 在正确分割时不允许可变借用

Ian*_*kel 4 immutability rust borrowing

struct Test {
    a: i32,
    b: i32,
}

fn other(x: &mut i32, _refs: &Vec<&i32>) {
    *x += 1;
}

fn main() {
    let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
    let mut refs: Vec<&i32> = Vec::new();
    for y in &xes {
        refs.push(&y.a);
    }
    xes.iter_mut().for_each(|val| other(&mut val.b, &refs));
}
Run Code Online (Sandbox Code Playgroud)

虽然refs仅保存对a元素的 -member 的引用xes并且函数other使用b-member,但 rust 会产生以下错误:

error[E0502]: cannot borrow `xes` as mutable because it is also borrowed as immutable
  --> /src/main.rs:16:5
   |
13 |     for y in &xes {
   |              ---- immutable borrow occurs here
...
16 |     xes.iter_mut().for_each(|val| other(&mut val.b, &refs));
   |     ^^^ mutable borrow occurs here                   ---- immutable borrow later captured here by closure
Run Code Online (Sandbox Code Playgroud)

操场

是不是关闭有问题?通常分摊借款应该允许这样做。我缺少什么?

Fra*_*gné 5

拆分借用仅适用于一个函数内部。不过,在这里,您借用了闭包中的字段amain字段b(除了能够从外部作用域使用和借用变量之外,它还是一个不同的函数)。

从 Rust 1.43.1 开始,函数签名无法表达细粒度的借用;当引用(直接或间接)传递给函数时,它可以访问所有引用。跨函数的借用检查基于函数签名;这部分是为了性能(跨函数的推理成本更高),部分是为了确保函数发展时的兼容性(尤其是在库中):函数的有效参数的构成不应该依赖于函数的实现

据我了解,您的要求是您需要能够b根据a整个对象集的字段值来更新对象的字段。

我看到有两种方法可以解决这个问题。首先,我们可以在捕获对 的b共享引用的同时捕获对 的所有可变引用a。这是分割借款的一个恰当例子。这种方法的缺点是我们需要分配两个Vecs 来执行操作。

fn main() {
    let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
    let mut x_as: Vec<&i32> = Vec::new();
    let mut x_bs: Vec<&mut i32> = Vec::new();
    for x in &mut xes {
        x_as.push(&x.a);
        x_bs.push(&mut x.b);
    }
    x_bs.iter_mut().for_each(|b| other(b, &x_as));
}
Run Code Online (Sandbox Code Playgroud)

这是使用迭代器构建两个Vecs 的等效方法:

fn main() {
    let mut xes: Vec<Test> = vec![Test { a: 3, b: 5 }];
    let (x_as, mut x_bs): (Vec<_>, Vec<_>) =
        xes.iter_mut().map(|x| (&x.a, &mut x.b)).unzip();
    x_bs.iter_mut().for_each(|b| other(b, &x_as));
}
Run Code Online (Sandbox Code Playgroud)

另一种方法是完全避免可变引用并使用内部可变性。标准库有Cell,它适用于Copy诸如i32RefCell,它适用于所有类型,但在运行时进行借用检查,增加了一些轻微的开销,和MutexRwLock它可以在多个线程中使用,但在运行时执行锁定检查,因此最多一个线程可以随时访问内部值。

这是一个带有 的示例Cell。我们可以用这种方法消除两个临时Vec变量,并且可以将整个对象集合传递给函数,other而不仅仅是对a字段的引用。

use std::cell::Cell;

struct Test {
    a: i32,
    b: Cell<i32>,
}

fn other(x: &Cell<i32>, refs: &[Test]) {
    x.set(x.get() + 1);
}

fn main() {
    let xes: Vec<Test> = vec![Test { a: 3, b: Cell::new(5) }];
    xes.iter().for_each(|x| other(&x.b, &xes));
}
Run Code Online (Sandbox Code Playgroud)