假设我使用嵌套闭包来修改局部变量,如下所示:

    let mut i = 0;

    let x = (0..).flat_map(|_| {
        (0..).map(|_| {
            let x = i;
            i += 1;
            x
        })
    });

这不能编译,并显示以下错误:

error: captured variable cannot escape `FnMut` closure body
  --> src/main.rs:35:9
   |
32 |       let mut i = 0;
   |           ----- variable defined here
33 |
34 |       let x = (0..).flat_map(|_| {
   |                                - inferred to be a `FnMut` closure
35 | /         (0..).map(|_| {
36 | |             let x = i;
   | |                     - variable captured here
37 | |             i += 1;
38 | |             x
39 | |         })
   | |__________^ returns a reference to a captured variable which escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape

我在非嵌套上下文中试验了相同的代码,它编译时没有出现错误:

    let mut i = 0;

    let x = (0..).map(|_| {
            let x = i;
            i += 1;
            x
    });

所以我猜错误来自于闭包是嵌套的,但我不能完全弄清楚为什么会触发错误.

推荐答案

让我们go 掉所有迭代器部分,只考虑正在构造的闭包.

    let mut i = 0;
    let f = || {
        || {
            let x = i;
            i += 1;
            x
        }
    };

i是由内部闭包使用的变量,因此由它borrow .现在,想一想如果我运行这个程序会发生什么:

let c1 = f();
let c2 = f();
[c1(), c2()]

c1c2都是捕获i and mutate it的闭包.因此,同时拥有它们允许共享可变状态,而不需要任何同步/互斥机制,这在Rust中总是被禁止的.编译上述代码将产生与您相同的错误.

Iterator::flat_map()实际上不会调用函数并像这样保留其中的两个,但它的签名不能将这一事实传达给编译器,所以编译器必须假设最坏的情况可能会发生.


既然我们知道允许这种模式是不合理的,为什么以您看到的方式描述错误呢?

当您在闭包中引用一个变量时,它(通常)会变成该变量的borrow.换句话说,调用|| { i += 1; }会构造一个包含指向i&mut i32的值.这意味着|| { i += 1; }本身是一个需要能够可变borrow i的表达式,因此包含|| { i += 1; }本身的闭包需要可变borrow i.

每当闭包包含可变的借入(或其他一些情况)时,这意味着调用闭包本身需要&mut self(基于相同的一般原则,您不能通过&来改变&mut).这就是为什么闭包被推断为FnMut(需要&mut self才能调用的函数类型)并且编译器会告诉您这一点的原因.

现在,为什么FnMut个闭包"只有在执行时才能访问它们捕获的变量"?我不确定如何解释这个问题的确切描述,但我确信它从根本上讲是关于&mut是唯一的要求,如果它被允许,最终会有某种方法来修改函数的状态,而它的结果仍然是borrow 的.


最后,你如何解决你的问题?嗯,在simple个 case 中,答案通常是将闭包设置为move || {个闭包.这意味着不再有借入,关闭owns现在是i计数器,所以它可以活得和关闭一样长.但在您的情况下,这并没有帮助,因为您实际上正在try (我假设)拥有一个在所有东西之间共享的计数器.因此,您需要一个内部可变性工具.对于这种情况,最简单的是Cell,它允许读写一个值(只要它是Copy),而不需要对它进行borrowing.

    use std::cell::Cell;

    let i = Cell::new(0);
    let x = (0..).flat_map(|_| {
        (0..).map(|_| {
            let x: i32 = i.get();
            i.set(x + 1);
            x
        })
    });

Rust相关问答推荐

Rust:跨多个线程使用hashmap Arc和rwlock

铁 rust 中的泛型:不能将`<;T作为添加>;::Output`除以`{Float}`

带参考文献的 rust 元组解构

我如何制作一个变异迭代器来锁定内部数据直到删除?

作为1字节位掩码的布尔值 struct

无符号整数的Rust带符号差

失真图像图形捕获Api

如何轮询 Pin>?

为什么将易错函数的泛型结果作为泛型参数传递 infer ()?不应该是暧昧的吗?

随机函数不返回随机值

如何为已实现其他相关 std trait 的每个类型实现一个 std Trait

Rust 打包在 .deb 中

是否可以在 Rust 中的特定字符上实现特征?

Rust 将特性传递给依赖项

没有通用参数的通用返回

在 Rust 中枚举字符串的最佳方式? (字符()与 as_bytes())

为什么这里需要类型注解?

如何在宏中的多个参数上编写嵌套循环?

返回引用的返回函数

为什么可以从不可变 struct 的字段中移动?