假设我使用嵌套闭包来修改局部变量,如下所示:
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()]
c1
和c2
都是捕获i
并使其突变的闭包。因此,同时拥有这两种机制可以在没有任何同步/互斥机制的情况下共享可变状态,而这在Rust中总是被禁止的。编译上面的代码将产生与您相同的错误。
Iterator::flat_map()
实际上不会像这样调用函数并保留其中两个,但它的签名无法将这一事实传达给编译器,因此编译器必须假设最坏的情况可能发生。
既然我们知道允许这种模式是不合理的,为什么会用你看到的方式描述错误?
当您在闭包中引用一个变量时,它(通常(会成为该变量的借位。换句话说,调用|| { i += 1; }
构造一个值,该值包含指向i
的&mut i32
。这意味着|| { i += 1; }
本身是一个需要能够可变地借用i
的表达式,因此包含|| { i += 1; }
本身的闭包需要i
的可变借用。
每当闭包包含可变借位(或其他一些情况(时,这意味着调用闭包本身需要&mut self
(基于相同的一般原则,即永远不能通过&
来变异&mut
(。这就是为什么闭包被推断为FnMut
s(需要&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
})
});