如何从回调中改变值?

  • 本文关键字:改变 回调 rust
  • 更新时间 :
  • 英文 :


对于这个基本的问题我很抱歉。

我正在尝试模拟类似web服务器调用的东西,并从外部范围捕获变量。

fn http_get(path: &str, f: &dyn Fn()) {
f();
}

fn main() {
let mut counter = 0;
http_get("/foo", &|| {
counter = counter + 1;
println!("/foo: {}", counter);
});

http_get("/bar", &|| {
counter = counter + 1;
println!("/bar: {}", counter);
});
}

这会导致编译错误cannot assign to 'counter', as it is a captured variable in a 'Fn' closure.

我知道我在一些基本的地方失败了,我有机会在正确的方向上得到推动吗?

首先,解释为什么这不起作用。

当您将counter转移到闭包中时,您基本上允许其他代码在任何时候使用它。如果它只是一个闭包,您可以将其move放入闭包中,将值的所有权赋予内部函数,并且它将正常工作,包括突变,因为没有办法违反借用检查器规则。

现在,当您有两个必须使用相同值的闭包时,并且您希望在两个闭包中都有对counter的可变引用时,借用检查器无法知道何时删除此可变引用。使用闭包的函数可以为以后保存闭包。那么你将有两个可变引用同时指向同一个值,这是借用检查器不允许的。

对于这种情况有几种解决方案。

  1. 如果你不跨线程发送闭包,你可以使用RefCell。它提供了内部可变性,因此即使您不将可变引用传递给闭包,您仍然可以改变counter的值。在这种情况下,借用检查器规则是在运行时检查的。如果你的代码试图同时获得两个可变引用,而不放弃前一个,你的代码将panic
fn http_get(path: &str, f: &dyn Fn()) {
f();
}
fn main() {
let counter = 0;
let r = std::cell::RefCell::new(counter);
http_get("/foo", &|| {
let mut counter = r.borrow_mut();
*counter = *counter + 1;
println!("/foo: {}", counter);
});
http_get("/bar", &move || {
let mut counter = r.borrow_mut();
*counter = *counter + 1;
println!("/bar: {}", counter);
});
}
  1. 如果你需要跨越线程边界,你可以使用ArcMutex作为@Zeppi回答建议。

  2. 如果您使用多个线程,并且您需要更改的值像counter一样简单,则可能应该使用Arc和atomics。它们允许多个线程在不锁的情况下进行可变访问。

您可以使用move关键字按值捕获闭包的环境(https://doc.rust-lang.org/std/keyword.move.html)。

但是,对于可变变量,你需要实现Mutex和Arc。

这样的

use std::sync::{Arc, Mutex};
fn http_get(path: &str, f: &dyn Fn()) {
f();
}
fn main() {
let counter = Arc::new(Mutex::new(5));
let c = Arc::clone(&counter);
http_get("/foo",  &( move || {
let mut c = c.lock().unwrap();
*c += 1;
println!("/foo: {}", c);
}));

let c = Arc::clone(&counter);
http_get("/bar",  &( move || {
let mut c = c.lock().unwrap();
*c += 1;
println!("/bar: {}", c);
}));
}

最新更新