返回InputOutputPin trait对象时遇编译错误:编译时类型大小未知
问题描述
我正在使用DHT11库,基于ESP32实现,Dht11::new方法的gpio参数必须同时实现InputPin和OutputPin trait。
于是我定义了一个包含这两个trait的超级trait:
trait InputOutputPin: InputPin<Error = EspError> + OutputPin<Error = EspError> {} impl<T: InputPin<Error = EspError> + OutputPin<Error = EspError>> InputOutputPin for T {}
接着写了根据引脚号返回该trait对象引用的函数:
fn get_gpio(pin: &u8) -> Result<&dyn InputOutputPin, &'static str>{ match pin { 33 => Ok(&Peripherals::take().unwrap().pins.gpio33.into_input_output().unwrap()), 32 => Ok(&Peripherals::take().unwrap().pins.gpio32.into_input_output().unwrap()), 27 => Ok(&Peripherals::take().unwrap().pins.gpio27.into_input_output().unwrap()), 26 => Ok(&Peripherals::take().unwrap().pins.gpio26.into_input_output().unwrap()), 25 => Ok(&Peripherals::take().unwrap().pins.gpio25.into_input_output().unwrap()), _ => Err("Pin not configurable for dht") } }
但调用Dht11::new时:
let gpio = Self::get_gpio(pin).unwrap(); let dht = Dht11::new(*gpio);
报错:doesn't have a size known at compile-time。
由于ESP32的GPIO每个都是独立结构体,我想根据引脚号动态创建DHT11实例,求问题原因和优化方案。
问题原因
- 动态大小类型限制:
dyn InputOutputPin属于动态大小类型(DST),解引用&dyn InputOutputPin得到的是DST实例,而Dht11::new需要的是编译期大小固定的具体类型——因为DHT11库的new方法会直接持有GPIO实例,DST无法在栈上分配或直接作为参数传递。 - 悬垂引用风险:
get_gpio中每次调用Peripherals::take()都会获取外设所有权,into_input_output()也会转移引脚所有权,函数结束后临时对象会被销毁,返回的引用会变成悬垂引用,属于未定义行为。 - 重复调用
Peripherals::take():该函数只能被调用一次,原代码每个match分支都调用会直接触发panic。
优化实现方案
方案1:枚举封装支持的GPIO类型(推荐嵌入式场景)
既然支持的引脚数量有限,用枚举包裹所有可能的GPIO类型,为枚举实现InputPin和OutputPin trait,这样既能保持静态类型的性能,又能实现动态选择引脚:
use esp_idf_hal::gpio::{InputPin, OutputPin, EspError, InputOutput}; use dht11::Dht11; use esp_idf_sys::Peripherals; // 枚举所有支持的DHT引脚类型 enum DhtPin { Gpio25(esp_idf_hal::gpio::Gpio25<InputOutput>), Gpio26(esp_idf_hal::gpio::Gpio26<InputOutput>), Gpio27(esp_idf_hal::gpio::Gpio27<InputOutput>), Gpio32(esp_idf_hal::gpio::Gpio32<InputOutput>), Gpio33(esp_idf_hal::gpio::Gpio33<InputOutput>), } // 为枚举实现InputPin trait impl InputPin for DhtPin { type Error = EspError; fn is_high(&self) -> Result<bool, Self::Error> { match self { DhtPin::Gpio25(pin) => pin.is_high(), DhtPin::Gpio26(pin) => pin.is_high(), DhtPin::Gpio27(pin) => pin.is_high(), DhtPin::Gpio32(pin) => pin.is_high(), DhtPin::Gpio33(pin) => pin.is_high(), } } fn is_low(&self) -> Result<bool, Self::Error> { match self { DhtPin::Gpio25(pin) => pin.is_low(), DhtPin::Gpio26(pin) => pin.is_low(), DhtPin::Gpio27(pin) => pin.is_low(), DhtPin::Gpio32(pin) => pin.is_low(), DhtPin::Gpio33(pin) => pin.is_low(), } } } // 为枚举实现OutputPin trait impl OutputPin for DhtPin { type Error = EspError; fn set_high(&mut self) -> Result<(), Self::Error> { match self { DhtPin::Gpio25(pin) => pin.set_high(), DhtPin::Gpio26(pin) => pin.set_high(), DhtPin::Gpio27(pin) => pin.set_high(), DhtPin::Gpio32(pin) => pin.set_high(), DhtPin::Gpio33(pin) => pin.set_high(), } } fn set_low(&mut self) -> Result<(), Self::Error> { match self { DhtPin::Gpio25(pin) => pin.set_low(), DhtPin::Gpio26(pin) => pin.set_low(), DhtPin::Gpio27(pin) => pin.set_low(), DhtPin::Gpio32(pin) => pin.set_low(), DhtPin::Gpio33(pin) => pin.set_low(), } } } // 改写get_gpio函数,返回枚举实例 fn get_gpio(pin: u8) -> Result<DhtPin, &'static str> { let peripherals = Peripherals::take().unwrap(); match pin { 33 => Ok(DhtPin::Gpio33(peripherals.pins.gpio33.into_input_output().unwrap())), 32 => Ok(DhtPin::Gpio32(peripherals.pins.gpio32.into_input_output().unwrap())), 27 => Ok(DhtPin::Gpio27(peripherals.pins.gpio27.into_input_output().unwrap())), 26 => Ok(DhtPin::Gpio26(peripherals.pins.gpio26.into_input_output().unwrap())), 25 => Ok(DhtPin::Gpio25(peripherals.pins.gpio25.into_input_output().unwrap())), _ => Err("Pin not configurable for dht") } } // 使用示例 fn main() -> Result<(), EspError> { let target_pin = 33; let gpio = get_gpio(target_pin).unwrap(); let mut dht = Dht11::new(gpio); // 后续调用dht.read()等方法 Ok(()) }
方案2:Box包装trait对象(适合允许动态分发的场景)
如果Dht11::new支持接受Box<dyn InputOutputPin>类型参数,可以用Box包装引脚对象,绕过静态类型限制:
use esp_idf_hal::gpio::{InputPin, OutputPin, EspError}; use dht11::Dht11; use esp_idf_sys::Peripherals; // 为超级trait添加Send/Sync约束(嵌入式环境通常需要) trait InputOutputPin: InputPin<Error = EspError> + OutputPin<Error = EspError> + Send + Sync {} impl<T: InputPin<Error = EspError> + OutputPin<Error = EspError> + Send + Sync> InputOutputPin for T {} // 返回Box包装的trait对象 fn get_gpio(pin: u8) -> Result<Box<dyn InputOutputPin>, &'static str> { let peripherals = Peripherals::take().unwrap(); match pin { 33 => Ok(Box::new(peripherals.pins.gpio33.into_input_output().unwrap())), 32 => Ok(Box::new(peripherals.pins.gpio32.into_input_output().unwrap())), 27 => Ok(Box::new(peripherals.pins.gpio27.into_input_output().unwrap())), 26 => Ok(Box::new(peripherals.pins.gpio26.into_input_output().unwrap())), 25 => Ok(Box::new(peripherals.pins.gpio25.into_input_output().unwrap())), _ => Err("Pin not configurable for dht") } } // 使用示例 fn main() -> Result<(), EspError> { let target_pin = 33; let gpio = get_gpio(target_pin).unwrap(); let mut dht = Dht11::new(gpio); // 后续调用dht.read()等方法 Ok(()) }
注意:此方案要求Dht11的new方法支持动态分发参数,如果原库仅接受具体类型,方案1更可靠。
内容的提问来源于stack exchange,提问作者elhe
相关产品推荐
相关产品推荐

