Rust implement try_from for u8 enum

Viewed 573

I have the following code where I need to do direct comparisons between the ranks. For example I need to be able to do self as u8 + 1 == other as u8.

#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(u8)]
pub enum Rank {
    Ace = 1,
    Two,
    Three,
    Four,
    Five,
    Six,
    Seven,
    Eight,
    Nine,
    Ten,
    Jack,
    Queen,
    King,
}

impl TryFrom<u8> for Rank {
    type Error = ();

    // TODO: replace with macro or find better option
    fn try_from(v: u8) -> Result<Self, Self::Error> {
        match v {
            x if x == Rank::Ace as u8 => Ok(Rank::Ace),
            x if x == Rank::Two as u8 => Ok(Rank::Two),
            x if x == Rank::Three as u8 => Ok(Rank::Three),
            x if x == Rank::Four as u8 => Ok(Rank::Four),
            x if x == Rank::Five as u8 => Ok(Rank::Five),
            x if x == Rank::Six as u8 => Ok(Rank::Six),
            x if x == Rank::Seven as u8 => Ok(Rank::Seven),
            x if x == Rank::Eight as u8 => Ok(Rank::Eight),
            x if x == Rank::Nine as u8 => Ok(Rank::Nine),
            x if x == Rank::Ten as u8 => Ok(Rank::Ten),
            x if x == Rank::Jack as u8 => Ok(Rank::Jack),
            x if x == Rank::Queen as u8 => Ok(Rank::Queen),
            x if x == Rank::King as u8 => Ok(Rank::King),
            _ => Err(()),
        }
    }
}

Is there a more efficient way to write this without using a macro and basically writing it all out anyway?.

1 Answers

tl;dr: Yes, there is a way to do this without macros, but it's unsafe. Macros are fine; use num_enum instead.


If you are willing to delve into the realm of unsafe code, you can use std::mem::transmute() to convert the u8 to Rank:

fn try_from(v: u8) -> Result<Self, Self::Error> {
    match v {
        x if x >= Rank::Ace as u8 && x <= Rank::King as u8 =>
            Ok(unsafe { std::mem::transmute(x) }),
        _ => Err(()),
    }
}

Beware, if the enum values change later and x >= Rank::Ace as u8 && x <= Rank::King as u8 no longer guarantees that the value is a valid enum value, undefined behavior will result if a bad value is converted.

If you take this approach, I would put very obvious warning comments on the definition of Rank so that others (and future-you) know that changing the values without suitably updating the try_from implementation could cause UB.

From the std::mem::transmute() documentation:

transmute is incredibly unsafe. There are a vast number of ways to cause undefined behavior with this function. transmute should be the absolute last resort.

This is a trade-off of saving a mere 11-12 lines of code at the cost of potentially sabotaging yourself later. I'm giving this answer for the sake of completeness, to say "yes, there is a way to do what you ask, but you really shouldn't do it this way."

Related