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

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.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 07:19:54