Lifetime issue using a callback closure

Hi everyone!

I am writing a smart-home-like application to control shutters using a Raspberry Pi Zero and a MQTT server. The logic you can see below is designed to stop moving shutters after a certain amount of time.

Code

enum ShutterStatus {
    STOPPED,
    MOVING(Timer),
}

...

fn action(&mut self, action: ShutterAction) -> Result<(), Error> {
    match action {
        ShutterAction::STOP => {
            self.status = ShutterStatus::STOPPED;
            self.up_gpio.switch(&GpioStatus::LOW);
            self.down_gpio.switch(&GpioStatus::LOW);
        },
        ShutterAction::UP => {
            self.status =
                ShutterStatus::MOVING(Timer::new(20, Box::new(move || self.action(ShutterAction::STOP))));
            self.up_gpio.switch(&GpioStatus::HIGH);
            self.down_gpio.switch(&GpioStatus::LOW);
            },
        ShutterAction::DOWN => {
            	(...)
        }
    }

    Ok(())
}

Problem

However, the code does not compile:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
  --> src/shutter.rs:44:30
   |
44 |                     Box::new(move || self.action(ShutterAction::STOP)),
   |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 34:5...
  --> src/shutter.rs:34:5
   |
34 | /     fn action(&mut self, action: ShutterAction) -> Result<(), Error> {
35 | |         match action {
36 | |             ShutterAction::STOP => {
37 | |                 self.status = ShutterStatus::STOPPED;
...  |
52 | |         Ok(())
53 | |     }
   | |_____^
   = note: ...so that the types are compatible:
           expected &mut shutter::Shutter
              found &mut shutter::Shutter
   = note: but, the lifetime must be valid for the static lifetime...
   = note: ...so that the expression is assignable:
           expected std::boxed::Box<std::ops::Fn() -> std::result::Result<(), error::Error> + std::marker::Send + 'static>
              found std::boxed::Box<std::ops::Fn() -> std::result::Result<(), error::Error> + std::marker::Send>

error: aborting due to previous error

For more information about this error, try `rustc --explain E0495`.
error: Could not compile `shutter-control`.

To learn more, run the command again with --verbose.

As far as I unterstand the problem, it cannot be garanteed self.action(...) still exists after the timer finished, so the compiler forbids this code. I'm running out of ideas how to fix this issue, any idea?
Thanks in advance!

Not quite. The problem is that you're trying to move self into the closure, whilst still using it outside. However, even that's not the most majorest issue:

self.status = ShutterStatus::MOVING(Timer::new(20, Box::new(move || self.action(ShutterAction::STOP))));

You're trying to put a reference to self inside of self. Rust will not let you do this. Rust absolutely loathes reference cycles, and will kick your ass six ways to Sunday if you try it.

The correct fix to this is to redesign your code to not need cyclic references. Do you need a closure here? Can you just use some kind of enum with the available actions defined in it? Can you make the closure take self as an argument that is provided when the closure is invoked so you don't need to capture self?

Failing that, if you really can't see any other way to do it: Rc<RefCell<_>> all the things.

3 Likes

These would be my suggestions as well. In the former case, I could see something like:

struct Timer {
    delay: i64, // or whatever type
    action: ShutterAction,
}

Then you'd store Timer::new(20, ShutterAction::STOP) and call action() with it when the timer elapses. As it happens, this also saves you a Box allocation as compared to the current attempt.

As an aside, the all-caps variant names in ShutterStatus are not the normal naming convention in Rust.

1 Like

Thanks a lot, folks. I rewrote the code - everything works fine now. I do have some minor redundancies now, but who cares. :wink:
Furthermore I changed my variant names, somehow the syntax highlighting in Sublime Text preferred them in all-caps.