Converting Enum to integer

Hi,

I am writing a parser/generator for the ads-l protocol which where I'm using enums internally. So internally I store the aircraft category as enum and whenever I have to pack the data I am using the integer value. So my question is what is the best way to implement the bridge between integer and the enum?
When I implement From I have automatically the Into as written in the documentation. The problem is like visible in the example below, it could be still converted directly to an integer without using the Into trait and therefore it could lead to wrong values (If the enums are not in the correct order for example)

pub enum AircraftCategory {
    Unknown,
    LightFixedWing,
    HeavyFixedWing,
    Rotorcraft,
    Sailplane,
    LighterThanAir,
    Ultralight,
    HangAndParaglider,
    Parachute,
    EVTOLAndUAM,
    Gyrocopter,
    UASOpenCategory,
    UASSpecific,
    UASCertified,
}


// If you use `main()`, declare it as `pub` to see it in the output:
impl From<usize> for AircraftCategory {
    fn from(value: usize) -> Self {
        match value {
            0 => Self::Unknown,
            1 => Self::LightFixedWing,
            2 => Self::HeavyFixedWing,
            3 => Self::Rotorcraft,
            4 => Self::Sailplane,
            5 => Self::LighterThanAir,
            6 => Self::Ultralight,
            7 => Self::HangAndParaglider,
            8 => Self::Parachute,
            9 => Self::EVTOLAndUAM,
            10 => Self::Gyrocopter,
            11 => Self::UASOpenCategory,
            12 => Self::UASSpecific,
            13 => Self::UASCertified,
            _ => Self::Unknown,
        }
    }
}

pub fn main() {
    let a = AircraftCategory::Parachute as u8;

    println!("{}", a)
}

I wonder if something like this would help, that is, explicitly assign the discriminant value to each enum variant:

pub enum AircraftCategory {
    Unknown = 0,
    LightFixedWing=2,
    HeavyFixedWing=1,
    Rotorcraft=3,
}

But in this case I still have to implement the From trait, because I wanna convert from integer to the enum as well. In this case the numbering would be redundant which could be error prone

You could add a "dummy" variant to enum with some data (even zero-sized, like Dummy(())) - this will prevent the as cast, as it will become non-trivial. This has the cost of matching on the never-created variant, of course.

You can cast the integers back to their enum variant.

I tried the following, but it seems not to work using contants in the match. It would be elegant if it would be possible to disallow to directly converting an enum to the integer, but this is slower than using directly the int value of the enum

pub enum AircraftCategory {
    Unknown = 0,
    LightFixedWing = 1,
    HeavyFixedWing = 2,
    Rotorcraft = 3,
    Sailplane = 4,
    LighterThanAir = 5,
    Ultralight = 6,
    HangAndParaglider = 7,
    Parachute = 8,
    EVTOLAndUAM = 9,
    Gyrocopter = 10,
    UASOpenCategory = 11,
    UASSpecific = 12,
    UASCertified = 13,
}


// If you use `main()`, declare it as `pub` to see it in the output:
impl From<usize> for AircraftCategory {
    fn from(value: usize) -> Self {
        const Unknown: usize = AircraftCategory::Unknown as usize;
        const LightFixedWing: usize = AircraftCategory::LightFixedWing as usize;
        const HeavyFixedWing: usize = AircraftCategory::HeavyFixedWing as usize;
        const Rotorcraft: usize = AircraftCategory::Rotorcraft as usize;
        const Sailplane: usize = AircraftCategory::Sailplane as usize;
        const Ultralight: usize = AircraftCategory::Ultralight as usize;
        const HangAndParaglider: usize = AircraftCategory::HangAndParaglider as usize;
        const Parachute: usize = AircraftCategory::Parachute as usize;
        const EVTOLAndUAM: usize = AircraftCategory::EVTOLAndUAM as usize;
        const Gyrocopter: usize = AircraftCategory::Gyrocopter as usize;
        const UASOpenCategory: usize = AircraftCategory::UASOpenCategory as usize;
        const UASSpecific: usize = AircraftCategory::UASSpecific as usize;
        const UASCertified: usize = AircraftCategory::UASCertified as usize;
    
        match value {
            Unknown => Self::Unknown,
            LightFixedWing => Self::LightFixedWing,
            HeavyFixedWing => Self::HeavyFixedWing,
            Rotorcraft => Self::Rotorcraft,
            Sailplane => Self::Sailplane,
            LighterThanAir => Self::LighterThanAir,
            Ultralight => Self::Ultralight,
            HangAndParaglider => Self::HangAndParaglider,
            Parachute => Self::Parachute,
            EVTOLAndUAM => Self::EVTOLAndUAM,
            Gyrocopter => Self::Gyrocopter,
            UASOpenCategory => Self::UASOpenCategory,
            UASSpecific => Self::UASSpecific,
            UASCertified => Self::UASCertified,
            _ => Self::Unknown,
        }
    }
}

pub fn main() {
    let a = AircraftCategory::Parachute as u8;

    println!("{}", a)
}

EDIT:
Actually, that isn't the correct solution.

impl From<usize> for AircraftCategory {
    fn from(value: usize) -> Self {
value.into()
}

This will have an infinite recursion, since Into::into will call From::from unless overridden (and overriding Into stops you form implementing From).

Yep, I've just realized that and edited.

I believe that crate enum_primitive could be a good fit for this scenario.

You could do it like this:

impl From<usize> for AircraftCategory {
    fn from(value: usize) -> Self {
        match value {
            0 => Self::Unknown,
            1 => Self::LightFixedWing,
            2 => Self::HeavyFixedWing,
            3 => Self::Rotorcraft,
            4 => Self::Sailplane,
            5 => Self::LighterThanAir,
            6 => Self::Ultralight,
            7 => Self::HangAndParaglider,
            8 => Self::Parachute,
            9 => Self::EVTOLAndUAM,
            10 => Self::Gyrocopter,
            11 => Self::UASOpenCategory,
            12 => Self::UASSpecific,
            13 => Self::UASCertified,
            _ => Self::Unknown,
        }
    }
}

Or using strum's FromRepr:

use strum::FromRepr;

#[derive(FromRepr, Debug, PartialEq)]
#[repr(u8)]
pub enum AircraftCategory {
    Unknown = 0,
    LightFixedWing = 1,
    HeavyFixedWing = 2,
    Rotorcraft = 3,
    Sailplane = 4,
    LighterThanAir = 5,
    Ultralight = 6,
    HangAndParaglider = 7,
    Parachute = 8,
    EVTOLAndUAM = 9,
    Gyrocopter = 10,
    UASOpenCategory = 11,
    UASSpecific = 12,
    UASCertified = 13,
}

fn main() {
    let a = AircraftCategory::from_repr(2);
}
1 Like

I might suggest the match value => Self::Unknown(value), so that applications can take advantage of abnormal categories even if they aren't supported by this library. The Unknown variant would need to be specified as Unknown(usize) or similar, a well.

Alternately, if this should not be supported, this arm could signal an error - which would require that the method be able to return one, which rules out From. TryFrom or a non-trait conversion method may be more appropriate in that case.

2 Likes

I'd prefer explicit values to be defined in the enum and any of enum_primitive, num_derive or strum.

1 Like

Thanks I will have a look on them

Good idea thanks!

#[derive(Debug)]
enum Test{
    Unknown = 0,
    A = 1,
    B = 2,
    C = 3,
}

impl From<usize> for Test {
    fn from(value: usize) -> Self {
        match value {
            _ if value == Test::Unknown as usize => Test::Unknown,
            _ if value == Test::A as usize => Test::A,
            _ if value == Test::B as usize => Test::B,
            _ if value == Test::C as usize => Test::C,
            _ => Test::Unknown,
        }
    }
}

pub fn main() { 

    let t = Test::A;

    let ti = t as u32;

    println!("{}", ti);

    let t2: Test = 2.into();
    println!("Test::{:?}", t2);
 }

I use match guards to make it more readable

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.