Search code examples
rustcallbackclosureslifetimefn

Capturing FnMut closure doesn't live long enough


Problem

In short: I'm trying to implement an struct that contains a callback to an closure, which captures state mutably. Idea is that user provides callback (closure) and can be informed later when a specific event happens.

I have a working proof-of-concept version (code sample #1).

This PoC works (code sample #1):

fn fun<F>(f: F) -> F
    where
        F: FnMut() -> (),
{
    f
}

fn main() {
    let mut abc = "abc".to_string();
    let mut f = fun(|| {
        abc.push_str(".");
        println!("closure: {:?}", abc);
    });
    f();
    f();
    f();
    println!("end: {:?}", abc);
}

Output:

closure: "abc."
closure: "abc.."
closure: "abc..."
end: "abc..."

This fails (code sample #2):

Same idea (slightly different) as previous but trying to contain closure inside Foo.

struct Foo<'a, T> {
    pub cb: Box<dyn FnMut(&T) + 'a>,
}

impl<'a, T> Foo<'a, T> {
    fn new(f: impl FnMut(&T) + 'a) -> Self {
        Self { cb: Box::new(f) }
    }
    fn on_change(&mut self, f: impl FnMut(&T) + 'a)
    {
        self.cb = Box::new(f);
    }
}

impl<'a, T> Default for Foo<'a, T>
{
    fn default() -> Self {
        Self::new(|_| {})
    }
}

fn main() {
    let mut abc = "abc".to_string();
    let mut f = Foo::default();
    f.on_change(|a| {
        abc.push_str("."); // PROBLEM HERE; uncomment and it works!
        println!("- param: {:?}", a);
    });
    let param = "a".to_string();
    (f.cb)(&param);
    (f.cb)(&param);
    (f.cb)(&param);
    println!("end: {:?}", abc);
}

Expected output:

- param: "a"
- param: "a"
- param: "a"
end: "abc..."

Actual output (compiler error):

error[E0502]: cannot borrow `abc` as immutable because it is also borrowed as mutable
  --> src/main.rs:33:27
   |
25 |     f.on_change(|a| {
   |                 --- mutable borrow occurs here
26 |         abc.push_str("."); // PROBLEM HERE; uncomment and it works!
   |         --- first borrow occurs due to use of `abc` in closure
...
33 |     println!("end: {:?}", abc);
   |                           ^^^ immutable borrow occurs here
34 | }
   | - mutable borrow might be used here, when `f` is dropped and runs the destructor for type `Foo<'_, String>`

The compiler error is pretty clear and it definitely has something to do with lifetimes. I think my problem is that I need to tell the compiler about the closure and its parameter lifetimes – but the question is, how?

How should I modify code sample #2 to get callback registration work like in code sample #1?

REPL:


Solution

  • abc is still bound in f.

    Variables get destroyed at the end of the scope they live in. f lives in main, so f gets destroyed at the end of main, which means that during the println, f still lives and binds abc.

    To destroy a variable earlier, you have to possibilities:

    • call drop on it
    • put it in a nested scope

    Here with drop:

    struct Foo<'a, T> {
        pub cb: Box<dyn FnMut(&T) + 'a>,
    }
    
    impl<'a, T> Foo<'a, T> {
        fn new(f: impl FnMut(&T) + 'a) -> Self {
            Self { cb: Box::new(f) }
        }
        fn on_change(&mut self, f: impl FnMut(&T) + 'a) {
            self.cb = Box::new(f);
        }
    }
    
    impl<'a, T> Default for Foo<'a, T> {
        fn default() -> Self {
            Self::new(|_| {})
        }
    }
    
    fn main() {
        let mut abc = "abc".to_string();
        let mut f = Foo::default();
        f.on_change(|a| {
            abc.push_str("."); // PROBLEM HERE; uncomment and it works!
            println!("- param: {:?}", a);
        });
        let param = "a".to_string();
        (f.cb)(&param);
        (f.cb)(&param);
        (f.cb)(&param);
    
        drop(f);
    
        println!("end: {:?}", abc);
    }
    

    And here with a nested scope:

    struct Foo<'a, T> {
        pub cb: Box<dyn FnMut(&T) + 'a>,
    }
    
    impl<'a, T> Foo<'a, T> {
        fn new(f: impl FnMut(&T) + 'a) -> Self {
            Self { cb: Box::new(f) }
        }
        fn on_change(&mut self, f: impl FnMut(&T) + 'a) {
            self.cb = Box::new(f);
        }
    }
    
    impl<'a, T> Default for Foo<'a, T> {
        fn default() -> Self {
            Self::new(|_| {})
        }
    }
    
    fn main() {
        let mut abc = "abc".to_string();
    
        {
            let mut f = Foo::default();
            f.on_change(|a| {
                abc.push_str("."); // PROBLEM HERE; uncomment and it works!
                println!("- param: {:?}", a);
            });
            let param = "a".to_string();
            (f.cb)(&param);
            (f.cb)(&param);
            (f.cb)(&param);
        }
    
        println!("end: {:?}", abc);
    }
    

    Both code snippets print:

    - param: "a"
    - param: "a"
    - param: "a"
    end: "abc..."