如何从 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(分别为 MutexRefCell)以放宽 a 的生命周期:Arc version[0], Rc version,这可能是最简单的,对 Target 的限制最少,尽管它会产生运行时成本。

或者你可以“作用域”Target 告诉 Rust 它没有逃逸,因此一切都完美无缺。我不确定这是最好的方法(希望其他人可以提供该信息)但是将 FnMuts 限制在一生中将允许 rustc 对此进行推理:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e67a4ab0faa8cc5d01c75293623c9fb4

这在运行时基本上是免费的,但这意味着Target无法真正逃脱它的功能。

所以前者可能是您想要的,后者对于事件/通知系统似乎不是特别有用,但是 YMMV。

[0] Atomic* 也适用于 Arc 版本,并且比互斥锁更容易和更便宜,尽管它可能与测试用例不太相关。