如何从 Rust 中的 Fn 闭包内部更改变量?
How to change the variable from inside Fn closure in Rust?
我有以下代码 (playground):
struct A {
pub vec: Vec<u64>,
}
impl A {
fn perform_for_all<F: Fn(&mut u64)>(&mut self, f: F) {
for mut i in &mut self.vec {
f(i);
}
}
}
fn main() {
let mut a = A {
vec: vec![1, 3, 44, 2, 4, 5, 6],
};
let mut done = false;
a.perform_for_all(|v| {
println!("value: {:?}", v);
done = true;
});
if !done {
a.perform_for_all(|v| {
println!("value {:?}", v);
});
}
}
出现以下错误:
error[E0594]: cannot assign to `done`, as it is a captured variable in a `Fn` closure
--> src/main.rs:21:9
|
21 | done = true;
| ^^^^^^^^^^^ cannot assign
|
help: consider changing this to accept closures that implement `FnMut`
--> src/main.rs:19:23
|
19 | a.perform_for_all(|v| {
| _______________________^
20 | | println!("value: {:?}", v);
21 | | done = true;
22 | | });
| |_____^
我有一个加载对象列表和一个数据库中的对象列表。我需要一个函数,它接受一个闭包并在加载的对象上执行它,如果列表中没有对象,则在数据库中的对象列表上执行它。
那个函数看起来像:
pub fn perform_for_match_with_mark<F>(&mut self, mark: MatchMark, f: F)
where
F: Fn(&mut GameMatch),
{
self.perform_for_all_matches(
|m| {
// runtime list
if let Game::Match(ref mut gm) = *m {
if gm.match_stamp().mark == mark {
f(gm);
}
}
},
None,
);
// if we have called `f` above - don't execute lines below.
let tx = self.match_tx.clone();
GamesDatabase::perform_for_match_with_mark(mark, |ms| {
// database
self.perform_for_all_matches(
|m| {
if let Game::Match(ref gm) = *m {
if gm.match_stamp().id == ms.id {
f(&mut GameMatch::new_with_match_stamp(
tx.clone(),
ms.clone(),
gm.needs_server_set,
gm.server_id,
))
}
}
},
None,
);
});
}
只有当我们无法在运行时列表中找到它们时,我们才必须对数据库中的对象进行操作。这就是为什么我决定创建一个变量 "we already found these objects in the list, leave the database alone".
更改 perform_for_all
函数以使用 FnMut
instead of Fn
:
fn perform_for_all<F>(&mut self, mut f: F)
where
F: FnMut(&mut u64),
{
for mut i in &mut self.vec {
f(&mut i);
}
}
,编译器正在运行。
Fn::call
的签名是:
extern "rust-call" fn call(&self, args: Args) -> Self::Output
这需要对 self
的不可变引用,这就是为什么您不能修改任何捕获的变量的原因。
FnMut::call_mut
的签名允许您改变变量,因为它需要 &mut self
:
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output
通过将闭包从 Fn
更改为 FnMut
,您允许它修改其捕获的变量,前提是您传递给它的引用是可变的。
只是为了进一步扩展 SplittyDev 的回答。
当您使用闭包时,编译器会施展魔法让闭包访问其环境中的变量。实际上,它将创建一个新结构,其成员是您尝试访问的变量。
不完全是这样(实际上不会编译),但它在概念上是一个合理的近似值:
struct Closure_1 {
done: bool
}
impl FnMut<&mut u64> for Closure_1 {
fn call_mut(&mut self, v: &mut u64) {
println!("value: {:?}", v);
self.done = true;
}
}
当你调用它时,这些变量将被借用或复制(或者如果你使用 move
关键字移动)。
let mut c1 = Closure_1 { done : done };
a.perform_for_all(|v| c1.call(&v));
done = c1.done;
当闭包修改它的环境时,它不能是一个 Fn
因为它还必须改变自身的变量:
impl Fn<&mut u64> for Closure_1 {
fn call(&self, v: &mut u64) {
println!("value: {:?}", v);
self.done = true; // Can't do this because self is not a mutable ref
}
}
有关详细信息,请参阅 Rust 编程语言 section on closures and their environment。
我有以下代码 (playground):
struct A {
pub vec: Vec<u64>,
}
impl A {
fn perform_for_all<F: Fn(&mut u64)>(&mut self, f: F) {
for mut i in &mut self.vec {
f(i);
}
}
}
fn main() {
let mut a = A {
vec: vec![1, 3, 44, 2, 4, 5, 6],
};
let mut done = false;
a.perform_for_all(|v| {
println!("value: {:?}", v);
done = true;
});
if !done {
a.perform_for_all(|v| {
println!("value {:?}", v);
});
}
}
出现以下错误:
error[E0594]: cannot assign to `done`, as it is a captured variable in a `Fn` closure
--> src/main.rs:21:9
|
21 | done = true;
| ^^^^^^^^^^^ cannot assign
|
help: consider changing this to accept closures that implement `FnMut`
--> src/main.rs:19:23
|
19 | a.perform_for_all(|v| {
| _______________________^
20 | | println!("value: {:?}", v);
21 | | done = true;
22 | | });
| |_____^
我有一个加载对象列表和一个数据库中的对象列表。我需要一个函数,它接受一个闭包并在加载的对象上执行它,如果列表中没有对象,则在数据库中的对象列表上执行它。
那个函数看起来像:
pub fn perform_for_match_with_mark<F>(&mut self, mark: MatchMark, f: F)
where
F: Fn(&mut GameMatch),
{
self.perform_for_all_matches(
|m| {
// runtime list
if let Game::Match(ref mut gm) = *m {
if gm.match_stamp().mark == mark {
f(gm);
}
}
},
None,
);
// if we have called `f` above - don't execute lines below.
let tx = self.match_tx.clone();
GamesDatabase::perform_for_match_with_mark(mark, |ms| {
// database
self.perform_for_all_matches(
|m| {
if let Game::Match(ref gm) = *m {
if gm.match_stamp().id == ms.id {
f(&mut GameMatch::new_with_match_stamp(
tx.clone(),
ms.clone(),
gm.needs_server_set,
gm.server_id,
))
}
}
},
None,
);
});
}
只有当我们无法在运行时列表中找到它们时,我们才必须对数据库中的对象进行操作。这就是为什么我决定创建一个变量 "we already found these objects in the list, leave the database alone".
更改 perform_for_all
函数以使用 FnMut
instead of Fn
:
fn perform_for_all<F>(&mut self, mut f: F)
where
F: FnMut(&mut u64),
{
for mut i in &mut self.vec {
f(&mut i);
}
}
Fn::call
的签名是:
extern "rust-call" fn call(&self, args: Args) -> Self::Output
这需要对 self
的不可变引用,这就是为什么您不能修改任何捕获的变量的原因。
FnMut::call_mut
的签名允许您改变变量,因为它需要 &mut self
:
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output
通过将闭包从 Fn
更改为 FnMut
,您允许它修改其捕获的变量,前提是您传递给它的引用是可变的。
只是为了进一步扩展 SplittyDev 的回答。
当您使用闭包时,编译器会施展魔法让闭包访问其环境中的变量。实际上,它将创建一个新结构,其成员是您尝试访问的变量。
不完全是这样(实际上不会编译),但它在概念上是一个合理的近似值:
struct Closure_1 {
done: bool
}
impl FnMut<&mut u64> for Closure_1 {
fn call_mut(&mut self, v: &mut u64) {
println!("value: {:?}", v);
self.done = true;
}
}
当你调用它时,这些变量将被借用或复制(或者如果你使用 move
关键字移动)。
let mut c1 = Closure_1 { done : done };
a.perform_for_all(|v| c1.call(&v));
done = c1.done;
当闭包修改它的环境时,它不能是一个 Fn
因为它还必须改变自身的变量:
impl Fn<&mut u64> for Closure_1 {
fn call(&self, v: &mut u64) {
println!("value: {:?}", v);
self.done = true; // Can't do this because self is not a mutable ref
}
}
有关详细信息,请参阅 Rust 编程语言 section on closures and their environment。