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?
Your intuition is almost correct:
pin
is owned byPumpManager
, butPumpManager
is owned byrun_pump()
because that method takes ownership viaself
.There is a confluence of expectations here:
PumpPin::disable()
takes ownership viaself
, butesp_idf_svc::timer::EspTimerService::timer()
expects aFnMut()
, that is, it expects a closure that can be called multiple times (think of a periodic timer). But we can't move ownership ofself.pin
into the closure, movepin
out of the closure viapin.disable()
, and call the closure again - becausepin
would be gone by then. This is why the requirement forFnMut
is not satisfied.Essentially, your problem is that
EspTimerService::timer()
enforces a closure that can be called from periodic timers, whilePumpPin::disable()
enforces that it can be called only exactly once, yet is called from what could be a periodic timer; one might say theesp_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 aFnMut
as is.If you can't change
PumpPin::disable()
, you may moveself.pin
into anOption
and.take()
thepin
out of thatOption
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 returnNone
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: