Rust中HID枚举关联及整数与字符串映射的惯用实现
问题描述
目标
- 将(usagePage, usageID)整数对转换为Rust枚举变体,获取页面及具体用法的名称字符串用于调试。
- 以最符合Rust惯用风格的方式实现该逻辑。
- 复用
usbd_human_interface_device::page模块提供的枚举类型。 - 优雅处理映射失败:当整数超出枚举范围时,检测并返回提示未找到映射的字符串,涵盖usagePage查找失败或对应页面内usageID查找失败的情况。
具体需求
- 使用
usbd_human_interface_device::page中的Consumer、Desktop、Game等枚举,这些枚举的变体关联有数值(如Desktop::SystemPowerDown对应129)。 - 自行实现了
HIDPage枚举,需将整数对转换为HIDPage变体及对应页面的特定枚举(如(1,129)对应(HIDPage::Desktop, Desktop::SystemPowerDown))。
挑战
- 枚举类型需在运行时确定,第二个返回值需支持泛型,但尝试自定义trait未成功。
- 无法直接返回枚举类型,不确定如何应用相关解决方案。
- 当前实现过于冗长,寻求更简洁的写法。
现有实现代码
// 我的HIDPage枚举实现 use num_enum::{TryFromPrimitive}; #[repr(u16)] #[derive( Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, TryFromPrimitive, )] pub enum HIDPage { Desktop = 1, Simulation = 2, Game = 5, Keyboard = 7, Telephony = 0x0b, Consumer = 0x0c, }
use usbd_human_interface_device::page::*; // 这个枚举加上HIDPage显得过于冗长 #[derive(Debug)] pub enum HIDEnum { Desktop(Desktop), Simulation(Simulation), Game(Game), Keyboard(Keyboard), Telephony(Telephony), Consumer(Consumer), } fn new_usage_from_ints(page:u16 , id: u16) -> Result<String, Box<dyn Error>> { let knownPage: Option<HIDPage> = HIDPage::try_from(page as u16).ok(); if let Some(knownPage) = knownPage.as_ref() { let knownId: Option<HIDEnum> = match knownPage { // 每一行都要在不同上下文重复三次类型 HIDPage::Desktop => Desktop::try_from(id as u8).ok().map(|v| HIDEnum::Desktop(v)), HIDPage::Simulation => Simulation::try_from(id as u8).ok().map(|v| HIDEnum::Simulation(v)), HIDPage::Game => Game::try_from(id as u8).ok().map(|v| HIDEnum::Game(v)), HIDPage::Keyboard => Keyboard::try_from(id as u8).ok().map(|v| HIDEnum::Keyboard(v)), HIDPage::Telephony => Telephony::try_from(id as u8).ok().map(|v| HIDEnum::Telephony(v)), HIDPage::Consumer => Consumer::try_from(id as u16).ok().map(|v| HIDEnum::Consumer(v)), }; return Ok(format!("nui {:?}/{:?} (0x{:x}/0x{:x})", knownPage, knownId, page, id)); } return Ok(format!("nui {:?} (0x{:x}/0x{:x})", knownPage, page, id)); }
优化方案
核心思路是通过自定义Trait统一处理所有页面枚举的转换逻辑,消除重复代码,同时简化错误处理和格式化流程。
步骤1:定义统一转换Trait
创建一个Trait,让所有usbd_human_interface_device::page下的枚举都实现它,提供从整数转换并关联对应页面的能力:
use num_enum::TryFromPrimitive; use std::fmt; use usbd_human_interface_device::page::*; // 统一处理HID页面枚举的转换与关联页面 trait HidUsage: TryFromPrimitive + fmt::Debug + Copy + Clone { // 从u16类型的ID尝试转换为当前枚举 fn try_from_id(id: u16) -> Option<Self>; // 返回当前枚举对应的HIDPage变体 fn page() -> HIDPage; }
步骤2:为各页面枚举实现Trait
针对每个页面枚举实现HidUsage,适配不同枚举的整数类型差异:
impl HidUsage for Desktop { fn try_from_id(id: u16) -> Option<Self> { Desktop::try_from(id as u8).ok() } fn page() -> HIDPage { HIDPage::Desktop } } impl HidUsage for Simulation { fn try_from_id(id: u16) -> Option<Self> { Simulation::try_from(id as u8).ok() } fn page() -> HIDPage { HIDPage::Simulation } } impl HidUsage for Game { fn try_from_id(id: u16) -> Option<Self> { Game::try_from(id as u8).ok() } fn page() -> HIDPage { HIDPage::Game } } impl HidUsage for Keyboard { fn try_from_id(id: u16) -> Option<Self> { Keyboard::try_from(id as u8).ok() } fn page() -> HIDPage { HIDPage::Keyboard } } impl HidUsage for Telephony { fn try_from_id(id: u16) -> Option<Self> { Telephony::try_from(id as u8).ok() } fn page() -> HIDPage { HIDPage::Telephony } } impl HidUsage for Consumer { fn try_from_id(id: u16) -> Option<Self> { Consumer::try_from(id).ok() } fn page() -> HIDPage { HIDPage::Consumer } }
步骤3:扩展HIDPage的格式化能力
为HIDPage添加方法,直接根据页面类型处理ID转换并生成调试字符串:
use num_enum::TryFromPrimitive; use std::error::Error; #[repr(u16)] #[derive( Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, TryFromPrimitive, )] pub enum HIDPage { Desktop = 1, Simulation = 2, Game = 5, Keyboard = 7, Telephony = 0x0b, Consumer = 0x0c, } impl HIDPage { // 根据当前页面类型,格式化对应的usage ID fn format_usage(&self, id: u16) -> String { match self { HIDPage::Desktop => format_usage_impl::<Desktop>(id), HIDPage::Simulation => format_usage_impl::<Simulation>(id), HIDPage::Game => format_usage_impl::<Game>(id), HIDPage::Keyboard => format_usage_impl::<Keyboard>(id), HIDPage::Telephony => format_usage_impl::<Telephony>(id), HIDPage::Consumer => format_usage_impl::<Consumer>(id), } } } // 通用格式化函数,复用所有页面的逻辑 fn format_usage_impl<T: HidUsage>(id: u16) -> String { match T::try_from_id(id) { Some(usage) => format!("{:?}/{:?}", T::page(), usage), None => format!("{:?}/未找到映射(0x{:x})", T::page(), id), } }
步骤4:重构主函数
现在new_usage_from_ints可以变得非常简洁,逻辑清晰:
fn new_usage_from_ints(page: u16, id: u16) -> Result<String, Box<dyn Error>> { let result = match HIDPage::try_from(page) { Ok(hid_page) => format!( "nui {} (0x{:x}/0x{:x})", hid_page.format_usage(id), page, id ), Err(_) => format!( "nui 未找到页面映射(0x{:x})/未找到映射(0x{:x})", page, id ), }; Ok(result) }
优化效果
- 消除了重复的转换逻辑,新增页面只需添加对应的
HidUsage实现即可。 - 错误提示更明确,区分页面未找到和ID未找到两种情况。
- 代码结构更符合Rust惯用风格,Trait的使用让逻辑复用性更强。
内容的提问来源于stack exchange,提问作者EB.
相关产品推荐
相关产品推荐

