如何从 Rust 闭包内部改变变量
How do I mutate a variable from inside a rust closure
我正在尝试用 Rust 实现一个类似于 EventListener
的界面。我有一个接受回调的特征,回调应该从它定义的范围内改变一个变量。但是我得到一个错误,说借用的值没有足够长的时间。
pub struct Target<T> {
funcs: Vec<Box<dyn FnMut(T) -> ()>>,
}
impl<T: Clone + 'static> Target<T> {
pub fn new() -> Target<T> {
return Target { funcs: Vec::new() };
}
pub fn add_listener(&mut self, func: Box<dyn FnMut(T) -> ()>) -> () {
self.funcs.push(Box::new(func));
}
pub fn trigger(&mut self, x: T) {
for callback in &mut self.funcs {
callback(x.clone());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trigger_mutation() {
let mut a = 0;
let mut t: Target<i32> = Target::new();
t.add_listener(Box::new(|x: i32| {
println!("{}", x);
a = x;
}));
t.trigger(42);
let b = a.clone();
assert_eq!(b, 42);
}
}
我运行它并得到这个:
$ cargo test -- --nocapture
Compiling luma-connector v0.1.0 (/home/blake/git/connector)
error[E0597]: `a` does not live long enough
--> src/target.rs:32:13
|
30 | t.add_listener(Box::new(|x: i32| {
| - -------- value captured here
| ________________________|
| |
31 | | println!("{}", x);
32 | | a = x + 1;
| | ^ borrowed value does not live long enough
33 | | }));
| |__________- cast requires that `a` is borrowed for `'static`
...
37 | }
| - `a` dropped here while still borrowed
But I get an error saying the borrowed value does not live long enough.
嗯,是的,你输入的 Target
没有任何关于作用域的暗示,所以就 Rust 类型系统而言,闭包可能会飞入 space 不受时间限制(例如 add_listener
可以将其传递给单独的线程),因此比 trigger_mutation
还长,这意味着 a
的寿命不够长。
有两种方法可以解决这个问题:
使用具有内部可变性的 Arc
/Rc
(分别为 Mutex
和 RefCell
)以放宽 a
的生命周期:Arc version[0], Rc version,这可能是最简单的,对 Target
的限制最少,尽管它会产生运行时成本。
或者你可以“作用域”Target
告诉 Rust 它没有逃逸,因此一切都完美无缺。我不确定这是最好的方法(希望其他人可以提供该信息)但是将 FnMut
s 限制在一生中将允许 rustc 对此进行推理:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e67a4ab0faa8cc5d01c75293623c9fb4
这在运行时基本上是免费的,但这意味着Target
无法真正逃脱它的功能。
所以前者可能是您想要的,后者对于事件/通知系统似乎不是特别有用,但是 YMMV。
[0] Atomic*
也适用于 Arc
版本,并且比互斥锁更容易和更便宜,尽管它可能与测试用例不太相关。
我正在尝试用 Rust 实现一个类似于 EventListener
的界面。我有一个接受回调的特征,回调应该从它定义的范围内改变一个变量。但是我得到一个错误,说借用的值没有足够长的时间。
pub struct Target<T> {
funcs: Vec<Box<dyn FnMut(T) -> ()>>,
}
impl<T: Clone + 'static> Target<T> {
pub fn new() -> Target<T> {
return Target { funcs: Vec::new() };
}
pub fn add_listener(&mut self, func: Box<dyn FnMut(T) -> ()>) -> () {
self.funcs.push(Box::new(func));
}
pub fn trigger(&mut self, x: T) {
for callback in &mut self.funcs {
callback(x.clone());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trigger_mutation() {
let mut a = 0;
let mut t: Target<i32> = Target::new();
t.add_listener(Box::new(|x: i32| {
println!("{}", x);
a = x;
}));
t.trigger(42);
let b = a.clone();
assert_eq!(b, 42);
}
}
我运行它并得到这个:
$ cargo test -- --nocapture
Compiling luma-connector v0.1.0 (/home/blake/git/connector)
error[E0597]: `a` does not live long enough
--> src/target.rs:32:13
|
30 | t.add_listener(Box::new(|x: i32| {
| - -------- value captured here
| ________________________|
| |
31 | | println!("{}", x);
32 | | a = x + 1;
| | ^ borrowed value does not live long enough
33 | | }));
| |__________- cast requires that `a` is borrowed for `'static`
...
37 | }
| - `a` dropped here while still borrowed
But I get an error saying the borrowed value does not live long enough.
嗯,是的,你输入的 Target
没有任何关于作用域的暗示,所以就 Rust 类型系统而言,闭包可能会飞入 space 不受时间限制(例如 add_listener
可以将其传递给单独的线程),因此比 trigger_mutation
还长,这意味着 a
的寿命不够长。
有两种方法可以解决这个问题:
使用具有内部可变性的 Arc
/Rc
(分别为 Mutex
和 RefCell
)以放宽 a
的生命周期:Arc version[0], Rc version,这可能是最简单的,对 Target
的限制最少,尽管它会产生运行时成本。
或者你可以“作用域”Target
告诉 Rust 它没有逃逸,因此一切都完美无缺。我不确定这是最好的方法(希望其他人可以提供该信息)但是将 FnMut
s 限制在一生中将允许 rustc 对此进行推理:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e67a4ab0faa8cc5d01c75293623c9fb4
这在运行时基本上是免费的,但这意味着Target
无法真正逃脱它的功能。
所以前者可能是您想要的,后者对于事件/通知系统似乎不是特别有用,但是 YMMV。
[0] Atomic*
也适用于 Arc
版本,并且比互斥锁更容易和更便宜,尽管它可能与测试用例不太相关。