`self` is a captured variable in an `FnMut` closure

243 views Asked by At

I'm trying to create a struct that enables a pin and then disables it after a period of time. I'm running into an issue of ownership and I'm not quite sure how to resolve it.

Here's the code I'm working with:

pub trait PumpPin {
    fn enable(self);
    fn disable(self);
}

impl PumpPin for Gpio4<Output> {
    fn enable(mut self: Gpio4<Output>) {
        self.set_high().unwrap();
    }

    fn disable(mut self: Gpio4<Output>) {
        self.set_low().unwrap();
    }
}

pub struct PumpManager<T: PumpPin + std::marker::Send + 'static + std::marker::Sync> {
    pin: T,
}

impl<T: PumpPin + std::marker::Send + 'static + std::marker::Sync> PumpManager<T> {
    pub fn new(pin: T) -> PumpManager<T> {
        PumpManager { pin }
    }

    pub fn run_pump(self, time: u64) -> Result<(), EspError> {
        self.pin.enable();

        // Configure the timer to disable the pin
        let once_timer = EspTimerService::new()
            .unwrap()
            .timer(move || self.pin.disable());

        //actually set a time for the timer
        once_timer.unwrap().after(Duration::from_secs(time))
    }
}

My goal was to encapsulate all of the logic that handles the timing of the pump.

The error I get below is at self.pin.disable()

cannot move out of `self.pin`, as `self` is a captured variable in an `FnMut` closure
move occurs because `self.pin` has type `T`, which does not implement the `Copy` trait

I thought the ownership of pin would belong to PumpManager itself but it actually belongs to whatever function is calling it. Is there a way I can refactor this to accomplish what I'm trying to do?

1

There are 1 answers

0
user2722968 On BEST ANSWER

Your intuition is almost correct: pin is owned by PumpManager, but PumpManager is owned by run_pump() because that method takes ownership via self.

There is a confluence of expectations here: PumpPin::disable() takes ownership via self, but esp_idf_svc::timer::EspTimerService::timer() expects a FnMut(), that is, it expects a closure that can be called multiple times (think of a periodic timer). But we can't move ownership of self.pin into the closure, move pin out of the closure via pin.disable(), and call the closure again - because pin would be gone by then. This is why the requirement for FnMut is not satisfied.

Essentially, your problem is that EspTimerService::timer() enforces a closure that can be called from periodic timers, while PumpPin::disable() enforces that it can be called only exactly once, yet is called from what could be a periodic timer; one might say the esp_idf_svc-API is poorly designed, but the contradiction is what it is.

If you can change PumpPin::disable() to not take ownership (but e.g. &mut self), your closure can become a FnMut as is.

If you can't change PumpPin::disable(), you may move self.pin into an Option and .take() the pin out of that Option inside the closure. The call to .take() will give you an owned value on the first call (the only time the timer executes), and would return None thereafter if the timer fired multiple times (which does not happen, but it would be correct as far as the compiler is concerned). For instance:

// Some FnMut, like the timer-callback
fn take<T: FnMut()>(_: T) {}

struct Foo {
    bar: String,
}

impl Foo {
    fn do_take(self) {
        // Destructure `self`, put `bar` into an `Option`
        let mut s = Some(self.bar);
        
        take(move || {
            // Move out of the `Option`.
            // If this closure was executed before (as a `FnMut` could),
            // this would not execute, because we leave a `None`
            if let Some(s) = s.take() {
                // We now own `s` and can call methods that take `self`.
                // But this path can only execute once, while the closure
                // as a whole can execute multiple times.
                println!("{s}");
            }
        })
    }
}