Goal:
- I’d like to turn a pair of (
usagePage
,usageID
) integers to Rust enumeration variants and get the names of both the page and specific usages as strings for debugging purposes. - I’d like to define this in the most idiomatic way.
- I’d like to reuse the enums provided by the
usbd_human_interface_device::page
module. - I’d like this mapping to fail gracefully – that is, given integers out of range of both enums, I’d like to detect that and return strings indicating that the mapping was not found.
- This failure case could be a failure to look up the
usagePage
or a failure to look up theusageID
within the correct enumeration.
- This failure case could be a failure to look up the
Concrete:
- I’m using the enum types from
usbd_human_interface_device::page
, namelyConsumer
,Desktop
,Game
and the like.- These enums have a numeric primitive association for each variant. For example,
Desktop::SystemPowerDown
is129
.
- These enums have a numeric primitive association for each variant. For example,
- That library does not provide an enum for each page, so I’ve written
HIDPage
below. - I’d like to turn a pair of integers into a
HIDPage
variant and the associated page-specific varient, such asDesktop::SystemPowerDown
- Each of those enum types are used when a
HIDPage
numeric enum variant specifies to do so. That is, given the tuple of integers(1, 129)
I’d like to return(HIDPage::Desktop,
Desktop::SystemPowerDown
)
.
Challenges:
- The type of enumeration must be determined at runtime, as the enum to look up depends on an integer that is not known until then.
- This implies that the second return value is generic over the set of enums I’d like to return, but I’m not sure how to write that. I tried defining a new trait that each enum implements but I couldn’t get that working.
- I’d like to return the enum type directly, but I think that’s not possible as enums are not types. /a/72438660/84041 seemed to have a similar question, but I’m not sure how to apply it here.
- I’d like to not be overly verbose in this mapping – I have a solution below, but it seems overly verbose. Is there a better way to write this?
Abstract:
- Is this the most idiomatic way to do this in Rust?
// My HIDPage enum implementation
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,
}
// This enumeration, plus HIDPage, seem overly verbose
#[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 {
// Each line requires repeating the type (in different contexts) three times
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));
}