调用一个借用为不可变的闭包时,不能在循环中借用为可变的吗?

Cannot borrow as mutable in a loop when calling a closure that borrows as immutable?

代码如下:

fn test(){
    let mut numbers = vec![2];
    let f = || {
        for _ in numbers.iter(){
        }
        false
    };

    while false {
        let res = f();
        if res {
            numbers.push(10);
        }
    }
}

错误是:

   |
15 |     let f = || {
   |             -- immutable borrow occurs here
16 |         for _ in numbers.iter(){
   |                  ------- first borrow occurs due to use of `numbers` in closure
...
22 |         let res = f();
   |                   - immutable borrow later used here
23 |         if res {
24 |             numbers.push(10);
   |             ^^^^^^^^^^^^^^^^ mutable borrow occurs here

但是如果我把while关键字改成if就可以编译了。如何解决这个问题?我想在循环中调用匿名函数。

不确定您要完成什么,但解决此问题的一种方法是使用 std::cell::RefCell(描述 in the std and in the book):

use std::cell::RefCell;

fn test(){
    let numbers = RefCell::new(vec![2]);
    let f = || {
        for _ in numbers.borrow().iter(){
        }
        false
    };

    while false {
        let res = f();
        if res {
            numbers.borrow_mut().push(10);
        }
    }
}

这里有一个稍微调整过的演示,它实际上做了一些事情:

use std::cell::RefCell;

fn main() {
    test();
}

fn test() {
    let numbers = RefCell::new(vec![0]);
    let f = || {
        for n in numbers.borrow().iter() {
            println!("In closure: {}", n);
        }
        println!();
        true
    };

    let mut i = 1;
    while i <= 3 {
        let res = f();
        if res {
            numbers.borrow_mut().push(i);
        }
        i += 1;
    }
    println!("End of test(): {:?}", numbers.borrow());
}

Output:

In closure: 0

In closure: 0
In closure: 1

In closure: 0
In closure: 1
In closure: 2

End of test(): [0, 1, 2, 3]

Rust Playground demo

我们可以通过用简单的不可变引用替换闭包来进一步简化您的示例

let mut numbers = vec![2];
let r = &numbers;

while false {
    println!("{:?}", r);
    numbers.push(10);
}

这里我们得到这个错误:

error[E0502]: cannot borrow `numbers` as mutable because it is also borrowed as immutable
 --> src/lib.rs:7:5
  |
3 | let r = &numbers;
  |         -------- immutable borrow occurs here
...
6 |     println!("{:?}", r); // use reference
  |                      - immutable borrow later used here
7 |     numbers.push(10);
  |     ^^^^^^^^^^^^^^^^ mutable borrow occurs here

就像您的示例一样,将 while 替换为 if 会使错误消失。为什么?

您可能知道重要的 Rust 规则:Aliasing nand mutability。它指出,在任何给定时间,一个值可以不可变地任意多次借用 只可变地借用一次。

语句numbers.push(10)临时可变地借用了numbers(只是为了语句)。但是我们还有 r 这是一个不可变的引用。为了使 numbers.push(10) 工作,编译器必须确保当时不存在其他借用。但是存在引用r!此引用不能与 numbers.push(10) 同时存在。

让我们先看看 if 的情况:

let mut numbers = vec![2];
let r = &numbers;            // <------+      (creation of r)   
                             //        |
if false {                   //        |
    println!("{:?}", r);     // <------+      (last use of r)
    numbers.push(10);
}

虽然词法范围意味着变量 r 仅在函数末尾被丢弃,但由于非词法生命周期,编译器可以看到最后一次使用 r 是在println 行。然后编译器可以在这一行之后将 r 标记为 "dead"。这反过来意味着,numbers.push(10) 行中没有其他借用,一切正常。

对于循环情况?让我们想象一下循环迭代三次:

let mut numbers = vec![2];
let r = &numbers;            // <------+      (creation of r)   
                             //        |
// First iteration           //        |
println!("{:?}", r);         //        |
numbers.push(10);            //        |  <== oh oh!
                             //        |
// Second iteration          //        |
println!("{:?}", r);         //        |
numbers.push(10);            //        |
                             //        |
// Third iteration           //        |
println!("{:?}", r);         // <------+     (last use of r)
numbers.push(10);

从这里可以看出,r活跃的时间与numbers.push(10)重叠(最后一个除外)。结果,编译器将产生一个错误,因为这段代码违反了 Rust 的中心规则。

对于闭包情况的解释是相同的:闭包不可变地借用 numbers 并且 f(); 使用该闭包。在循环情况下,编译器无法充分收缩闭包的 "alive time" 以确保它不会与 push.

的可变借用重叠

如何修复?

好吧,你可以每次都将 numbers 传递到闭包中:

let mut numbers = vec![2];
let f = |numbers: &[i32]| {
    for _ in numbers.iter(){
    }
    false
};

while false {
    let res = f(&numbers);
    if res {
        numbers.push(10);
    }
}

这是有效的,因为现在,numbers 被不变地借用,也只是暂时用于 f(&numbers); 语句。

您也可以使用 RefCell 作为其他答案的建议,但这应该是最后的选择。