You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,实际却似乎属于调用函数。请问如何重构代码实现需求?


解决方案

问题根源

  1. PumpPin trait的所有权设计缺陷:enable和disable方法接收self而非&mut self,这意味着每次调用都会消耗引脚实例,无法重复操作同一引脚。
  2. 闭包的所有权冲突:定时器闭包需要持有引脚所有权,但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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 03:25:33