当正确分裂时,Rust不允许可变借用

4
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));
}

尽管refs仅包含对xes中元素的a成员的引用,而函数other使用了b成员,但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

Playground

闭包有问题吗?通常情况下分裂借用应该是允许的,我错过了什么吗?

1个回答

5

分解借用仅在一个函数内部有效。但是,在这里,您正在main中借用a字段和闭包中的b字段(除了能够使用外部作用域的变量进行消耗和借用之外,闭包还是一个不同的函数)。

自Rust 1.43.1版本以来,函数签名无法表示细粒度借用;当引用被传递(直接或间接地)给函数时,它会获得对其所有内容的访问权。跨函数的借用检查基于函数签名;这在一定程度上是为了性能(函数间推断成本更高),另一方面则是为了确保兼容性(特别是在库中):函数的有效参数不应该依赖于函数的实现。

据我理解,您的要求是根据整组对象的字段a的值更新对象的字段b。

我看到有两种方法可以解决这个问题。首先,我们可以同时捕获对b的所有可变引用以及对a的共享引用。这是拆分借用的正确示例。这种方法的缺点是我们需要分配两个Vec才能执行操作。

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));
}

这是使用迭代器构建两个Vec的等效方式:
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));
}

另一种方法是完全避免可变引用,而是使用内部可变性。标准库有 Cell,适用于像 i32 这样的 Copy 类型,RefCell 适用于所有类型,但在运行时进行借用检查,增加了轻微的开销,而 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));
}

惊人而精细的回答!+1 - Ian Rehwinkel

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