5

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 Answer 1

7

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."

Sign up to request clarification or add additional context in comments.

2 Comments

"You really shouldn't do it this way" especially since the compiler is able to optimize the full match to a single compare and return anyway: godbolt
@Jmb Right. Although, I suspect OP's goal is not necessarily runtime efficiency, but code maintainability/terseness. It does feel a bit redundant to have to write out each match arm, so I understand the motivation -- which is why we have the num_enum crate.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.