嵌套的 FnMut "returns a reference to a captured variable which escapes the closure body"



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

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

所以我想这个错误是因为闭包是嵌套的,但我不能完全弄清楚为什么会触发这个错误。

让我们去掉所有迭代器部分,只考虑正在构建的闭包。

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

i是内部闭包使用的变量,因此被它借用

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

c1c2都是捕获i并使其突变的闭包。因此,同时拥有这两种机制可以在没有任何同步/互斥机制的情况下共享可变状态,而这在Rust中总是被禁止的。编译上面的代码将产生与您相同的错误。

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


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

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

每当闭包包含可变借位(或其他一些情况(时,这意味着调用闭包本身需要&mut self(基于相同的一般原则,即永远不能通过&来变异&mut(。这就是为什么闭包被推断为FnMuts(需要&mut self调用的函数类型(,编译器会告诉你这一点。

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


最后,你如何解决你的问题?在简单的情况下,答案通常是使闭包成为move || {闭包。这意味着不再有借债,而闭包现在拥有i计数器,因此它可以和闭包一样长。但在你的情况下,这并没有帮助,因为你实际上试图(我想(有一个在所有东西之间共享的计数器。因此,您需要一个内部可变性工具。对于这种情况,最简单的是Cell,它允许读取和写入一个值(只要它是Copy(,而不需要借用

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

相关内容

最新更新