Sending method as callback function to field object in Rust

Viewed 456

I want to implement a Timer object, which is owned by Foo object and it should call the Foo object's method periodically. I just implemented adding callback function (which is executing Foo object's method) to the Timer as below, but it failed to compile due to conflicting lifetime requirements.

Code (does not containing executing callback function part)

use std::sync::Mutex;

#[derive(Default)]
struct Timer<'a> {
    callbacks: Mutex<Vec<Box<dyn Fn() + 'a>>>,
}

impl<'a> Timer<'a> {
    fn add(&self, callback: Box<dyn Fn() + 'a>) {
        let mut callbacks = self.callbacks.lock().unwrap();

        callbacks.push(callback);
    }
}

#[derive(Default)]
struct Foo<'a> {
    value: usize,
    timer: Timer<'a>,
}

impl<'a> Foo<'a> {
    fn callback(&self) {
        println!("value is {}", self.value);
    }

    fn process(&self) {
        let callback = || {
            self.callback();
        };

        self.timer.add(Box::new(callback));
    }
}

fn main() {
    let foo = Foo::default();

    foo.process();
}

failed to compile with below error

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/main.rs:28:24
   |
28 |           let callback = || {
   |  ________________________^
29 | |             self.callback();
30 | |         };
   | |_________^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 27:5...
  --> src/main.rs:27:5
   |
27 |     fn process(&self) {
   |     ^^^^^^^^^^^^^^^^^
note: ...so that the types are compatible
  --> src/main.rs:28:24
   |
28 |           let callback = || {
   |  ________________________^
29 | |             self.callback();
30 | |         };
   | |_________^
   = note: expected `(&&Foo<'a>,)`
              found `(&&Foo<'a>,)`
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 22:6...
  --> src/main.rs:22:6
   |
22 | impl<'a> Foo<'a> {
   |      ^^
note: ...so that the types are compatible
  --> src/main.rs:32:20
   |
32 |         self.timer.add(Box::new(callback));
   |                    ^^^
   = note: expected `&Timer<'_>`
              found `&Timer<'a>`

error: aborting due to previous error

I think the error comes from the process method in Foo object takes self as &'_ lifetime, but self.timer need &'a lifetime.

When I added &'a lifetime to self in process and callback method as below,

...

impl<'a> Foo<'a> {
    fn callback(&'a self) {
        ...
    }

    fn process(&'a self) {
        ...
    }
}

...

Below error occured-

error[E0597]: `self` does not live long enough
  --> src/main.rs:29:13
   |
22 | impl<'a> Foo<'a> {
   |      -- lifetime `'a` defined here
...
28 |         let callback = || {
   |                        -- value captured here
29 |             self.callback();
   |             ^^^^ borrowed value does not live long enough
...
32 |         self.timer.add(Box::new(callback));
   |                        ------------------ cast requires that `self` is borrowed for `'a`
33 |     }
   |     - `self` dropped here while still borrowed

error[E0597]: `foo` does not live long enough
  --> src/main.rs:39:5
   |
39 |     foo.process();
   |     ^^^ borrowed value does not live long enough
40 | }
   | -
   | |
   | `foo` dropped here while still borrowed
   | borrow might be used here, when `foo` is dropped and runs the destructor for type `Foo<'_>`

error: aborting due to 2 previous errors

How to solve this problem?

1 Answers

The problem here is that you want to create a reference back to Foo inside Timer, but if Foo gets moved or if the Timer gets moved out of Foo while Foo is destroyed, this reference would be invalid. One (naive) way to fix this would be to use a reference counter for value and avoid referencing Foo directly:

#[derive(Default)]
struct Foo<'a> {
    value: Rc<usize>,
    timer: Timer<'a>,
}

impl<'a> Foo<'a> {
    fn process(&self) {
        let value_rc = self.value.clone();
        let callback = move || {
            println!("value is {}", *value_rc);
        };

        self.timer.add(Box::new(callback));
    }
}

This makes using value a bit more difficult and would have a runtime overhead in Rc (or Arc if multithreaded). A better solution would probably be to split the structs entirely and use the normal lifetime rules like this:

#[derive(Default)]
struct Foo {
    value: usize,
}

impl Foo {
    fn callback(&self) {
        println!("value is {}", self.value);
    }
}

fn main() {
    let foo = Foo::default();
    let timer = Timer::default();

    let callback = || {
        foo.callback();
    };

    timer.add(Box::new(callback));
}

This way, Rust will make sure that foo, timer and callback each live long enough, because of the order that they are created and destroyed in inside main().

Related