Rust中FnMut闭包捕获self.pin的所有权问题及重构咨询
问题描述
我试图创建一个结构体,用于启用引脚并在一段时间后将其禁用,但遇到所有权相关问题,不知如何解决。以下是我的代码:
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)) } }
遇到的错误:
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
我原以为pin的所有权属于PumpManager,实际却似乎属于调用函数。请问如何重构代码实现需求?
解决方案
问题根源
PumpPintrait的所有权设计缺陷:enable和disable方法接收self而非&mut self,这意味着每次调用都会消耗引脚实例,无法重复操作同一引脚。- 闭包的所有权冲突:定时器闭包需要持有引脚所有权,但
self已在run_pump中被移动,且Rust对定时器闭包的类型约束(兼容FnMut)不允许二次移动非Copy类型的self.pin。
重构步骤
1. 修改PumpPin trait,使用可变引用
将trait方法改为接收&mut self,保留引脚实例的所有权,支持重复操作:
pub trait PumpPin { fn enable(&mut self); fn disable(&mut self); } impl PumpPin for Gpio4<Output> { fn enable(&mut self) { self.set_high().unwrap(); } fn disable(&mut self) { self.set_low().unwrap(); } }
2. 用智能指针实现共享所有权
使用Arc<Mutex<T>>实现线程安全的共享可变访问,让PumpManager和定时器闭包同时持有引脚的引用:
use std::sync::{Arc, Mutex}; use esp_idf_svc::timer::EspTimerService; use esp_idf_sys::EspError; use core::time::Duration; pub struct PumpManager<T: PumpPin + Send + 'static> { pin: Arc<Mutex<T>>, } impl<T: PumpPin + Send + 'static> PumpManager<T> { pub fn new(pin: T) -> Self { PumpManager { pin: Arc::new(Mutex::new(pin)), } } pub fn run_pump(self, time: u64) -> Result<(), EspError> { // 获取锁并启用引脚 self.pin.lock().unwrap().enable(); // 克隆Arc,让闭包持有共享所有权 let pin_clone = Arc::clone(&self.pin); let once_timer = EspTimerService::new()? .timer(move || { // 定时器触发时获取锁并禁用引脚 pin_clone.lock().unwrap().disable(); }); once_timer?.after(Duration::from_secs(time)) } }
关键说明
Arc:提供线程安全的引用计数,允许PumpManager和定时器闭包共享引脚的所有权,避免移动冲突。Mutex:保证同一时间只有一个上下文能修改引脚,满足可变操作的线程安全要求。- 闭包捕获:通过克隆
Arc,闭包获得独立的引用计数实例,不会影响PumpManager对引脚的持有。
内容的提问来源于stack exchange,提问作者Chris Woolum
相关产品推荐
相关产品推荐

