简体   繁体   中英

How do I mutate a variable from inside a rust closure

I'm trying to implement an EventListener like interface in rust. I have a trait that takes a callback, the callback should mutate a variable from the scope it was defined in. But I get an error saying the borrowed value does not live long enough.

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);

    }
}

I run it and get this:

$ 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.

Well yes, your typing of Target implies nothing about scoping, so as far as the Rust typesystem is concerned, the closure could just fly into space unbouded by time (eg add_listener could pass it to a separate thread), therefore outlive trigger_mutation , which means a does not live long enough.

There are two ways to resolve this issue:

Use Arc / Rc with interior mutability (resp. Mutex and RefCell ) in order to relax the lifetime of a : Arc version [0], Rc version , this is probably the simplest, and the least restrictive on Target , though it comes at a runtime cost.

Alternatively you can "scope" Target to tell Rust that it doesn't escape, therefore everything's perfectly kosher. I'm not sure it's the best way (hopefully somebody else can contribute that information) but bounding the FnMut s on a lifetime will allow rustc to reason about this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e67a4ab0faa8cc5d01c75293623c9fb4

This is basically free at runtime, but it means Target can't really escape its function.

So the former is probably what you want, the latter seems not super-useful for an events / notification system, but YMMV.

[0] an Atomic* would also work for the Arc version and be a bit easier & cheaper than a mutex, though it probably isn't very relevant for a test case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM