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

时间:2019-08-28 06:54:23

标签: rust closures 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);
        }
    }
}

错误是:

   |
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,则可以对其进行编译。如何解决这个问题?我想循环调用匿名函数。

2 个答案:

答案 0 :(得分:6)

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

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规则:别名与变异性。它指出,在任何给定的时间,一个值要么可以多次不变地被任意借用,要么可以任意地一次精确地借用一次。

语句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的最后一次使用是在{ {1}}行。然后,编译器可以在此行之后将println标记为“死”。反过来,这意味着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); 处于活动状态的时间与r重叠(最后一个除外)。结果,编译器将产生错误,因为此代码违反了中心的Rust规则。

对于您的闭包情况,解释是相同的:闭包不可变地借用numbers.push(10),而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); } } 也是为numbers语句而临时借用的。

您也可以使用f(&numbers);作为其他答案,但这应该是最后的选择。

答案 1 :(得分:2)

不确定要完成什么,但是解决此问题的一种方法是使用products_tbl (product_id, product_manufacturer, product_name) manufacturers_tbl (manufacturer_id, manufacturer_name) (描述为in the std和{{3} }):

std::cell::RefCell

这里有一些经过调整的演示,它实际上可以执行以下操作:

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

in the book