调用借为不可变的闭包时,无法在循环中借为可变变量吗?

HYR*_*YRY 3 closures rust borrow-checker

这是代码:

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

    while false {
        let res = f();
        if res {
            numbers.push(10);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

错误是:

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

    while false {
        let res = f();
        if res {
            numbers.push(10);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我将while关键字更改为if,则可以对其进行编译。如何解决这个问题?我想在循环中调用匿名函数。

Luk*_*odt 6

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

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

while false {
    println!("{:?}", r);
    numbers.push(10);
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们得到这个错误:

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
Run Code Online (Sandbox Code Playgroud)

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

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

该报表是临时可变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);
}
Run Code Online (Sandbox Code Playgroud)

虽然词法作用域意味着变量r由于非词法生存期而仅在函数末尾删除,但编译器可以看到最后一次使用rprintln在行中。然后,编译器可以r在此行之后标记为“死”。反过来,这意味着该行中没有其他借方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);
Run Code Online (Sandbox Code Playgroud)

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

对于您的闭包情况,其解释是相同的:闭包numbers不可变地借用并f();使用该闭包。在循环情况下,编译器无法充分缩短关闭的“有效时间”,以确保它不会与的可变借位重叠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);
    }
}
Run Code Online (Sandbox Code Playgroud)

之所以起作用,numbers是因为现在,它也是为f(&numbers);语句而临时借用的。

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