【问题标题】:Rust not allowing mutable borrow when splitting properly正确拆分时 Rust 不允许可变借用
【发布时间】:2020-05-09 15:15:08
【问题描述】:
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

关闭有什么问题吗?通常splitting borrows 应该允许这样做。我错过了什么?

【问题讨论】:

    标签: rust immutability borrowing


    【解决方案1】:

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

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

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

    我看到了两种解决此问题的方法。首先,我们可以在捕获对a 的共享引用的同时捕获对b 的所有可变引用。这是拆分借款的一个恰当例子。这种方法的一个缺点是我们需要分配两个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));
    }
    

    这是使用迭代器构建两个 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));
    }
    

    另一种方法是完全避免可变引用,而是使用内部可变性。标准库有Cell,它适用于Copy 类型,例如i32RefCell,它适用于所有类型,但在运行时进行借用检查,增加了一些开销,以及Mutex 和@987654337 @,可以在多个线程中使用,但在运行时执行锁检查,因此在任何时候最多有一个线程可以访问内部值。

    下面是Cell 的示例。我们可以通过这种方法消除两个临时的Vecs,我们可以将整个对象集合传递给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
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-10
    • 1970-01-01
    • 1970-01-01
    • 2016-07-30
    • 1970-01-01
    相关资源
    最近更新 更多