Convert enum into/from number in Rust

One of the advantageous features of Rust is its enum types, which offer a strongly typed, multichoice option, thereby enhancing the flexibility of Rust code. Enums in Rust can accommodate values ranging from simple integers to complex structure types, per distinct enum arm. Enums introduce a mini-language syntax to the Rust codebase, requiring a separate study of pattern matching. While this syntax can be challenging for newcomers, I intend to address this issue using macros (as an example). See full code at Rust Online Playground This document provides my intention of enum_builder! macro in rustls crate. Why Enum Conversion? Enum conversion is essential in scenarios involving the marshalling or serialization of objects. This process typically requires the conversion of enums to and from integers, which occurs in various contexts such as database operations, API payloads, raw data, and more. What is the problem? In Rust, it is not possible to convert enums to integers. pub enum Season { Spring, Summer, Autumn, Winter, } fn main() { let season_id: u8 = Season::Autumn; println!("season id: {season_id}"); } error[E0308]: mismatched types --> src/main.rs:8:25 | 8 | let season_id: u8 = Season::Autumn; | -- ^^^^^^^^^^^^^^ expected `u8`, found `Season` | | | expected due to this One potential solution involves employing the #[repr(u8)] macro. #[repr(u8)] pub enum Season { Spring = 0, Summer = 1, Autumn = 2, Winter = 4, } fn main() { let season_id: u8 = Season::Autumn as _; println!("season id: {season_id}"); // ok // fails let session: Season = season_id as _; } Although this is a one-way solution, it does not address the reciprocal issue. Let’s propose implementing a macro to mitigate this problem. The final main function is as follows: enum_builder! { #[repr(u8)] #[derive(Copy, Clone, Debug)] pub enum Season { Spring => 0, Summer => 1, Autumn => 2, Winter => 4, } } fn main() { let season_id: u8 = Season::Autumn.into(); // let season_id = u8::from(Season::Autumn); // or this syntax println!("season id: {season_id}"); let season: Season = season_id.try_into().unwrap(); //let season = Season::try_from(season_id)().unwrap(); // or this syntax println!("season: {season:?}"); } Let's implement the enum_builder macro. macro_rules! enum_builder { ( // the target type to convert from/to. It must become at first before other meta tags #[repr($typ:ty)] // extra optional meta like #[derive(Debug)] $( #[$meta:meta] )* // accessor like pub or pub(crate) $access:vis enum $name:ident { // catch arms and assigned values like Sprint => 3 // that is: $arm = Spring and $val = 3 // I used literal not expr for later match expression. // If expr is used, you need to implement if/else instead of match. $( $arm:ident => $val:literal ),* $(,)? } ) => { // create the enum with specified arms without literal and values $( #[$meta] )* $access enum $name { $($arm,)* } // implement conversion from the target type to the enum. // Since the target type has a bigger domain, the conversion is fallible. // I mean, if target type be u8, there are 256 value for the type while or enum has much lesser arms, so // convesion from u8 to our enum may does not happen. impl TryFrom for $name { type Error = (); fn try_from(x: $typ) -> Result { match x { $( $val => Ok($name::$arm),)* _ => Err(()), } } } // implement convesion into target type. It always happens since the enum's arms are much lesser in count. impl From for $typ { fn from(enm: $name) -> Self { match enm { $($name::$arm => $val ,)* } } } }; } Online playground This macro is a customized version of the original macro implemented in the rustls crate. The unknown arm is removed, and conversion from the target type is fallible here. Conclusion: Although there is no straightforward native solution to convert enums to and from integer values, we can utilize macros to implement a performant solution.

Mar 17, 2025 - 19:40
 0
Convert enum into/from number in Rust

One of the advantageous features of Rust is its enum types, which offer a strongly typed, multichoice option, thereby enhancing the flexibility of Rust code. Enums in Rust can accommodate values ranging from simple integers to complex structure types, per distinct enum arm. Enums introduce a mini-language syntax to the Rust codebase, requiring a separate study of pattern matching. While this syntax can be challenging for newcomers, I intend to address this issue using macros (as an example).

See full code at Rust Online Playground

This document provides my intention of enum_builder! macro in rustls crate.

Why Enum Conversion?

Enum conversion is essential in scenarios involving the marshalling or serialization of objects. This process typically requires the conversion of enums to and from integers, which occurs in various contexts such as database operations, API payloads, raw data, and more.

What is the problem?

In Rust, it is not possible to convert enums to integers.

pub enum Season {
    Spring,
    Summer,
    Autumn,
    Winter,
}
fn main() {
    let season_id: u8 = Season::Autumn;
    println!("season id: {season_id}");
}
error[E0308]: mismatched types
 --> src/main.rs:8:25
  |
8 |     let season_id: u8 = Season::Autumn;
  |                    --   ^^^^^^^^^^^^^^ expected `u8`, found `Season`
  |                    |
  |                    expected due to this

One potential solution involves employing the #[repr(u8)] macro.

#[repr(u8)]
pub enum Season {
    Spring = 0,
    Summer = 1,
    Autumn = 2,
    Winter = 4,
}
fn main() {
    let season_id: u8 = Season::Autumn as _;
    println!("season id: {season_id}"); // ok

    // fails
    let session: Season = season_id as _;
}

Although this is a one-way solution, it does not address the reciprocal issue. Let’s propose implementing a macro to mitigate this problem.

The final main function is as follows:

enum_builder! {
    #[repr(u8)]
    #[derive(Copy, Clone, Debug)]
    pub enum Season {
        Spring => 0,
        Summer => 1,
        Autumn => 2,
        Winter => 4,
    }
}
fn main() {
    let season_id: u8 = Season::Autumn.into();
    // let season_id = u8::from(Season::Autumn); // or this syntax
    println!("season id: {season_id}");

    let season: Season = season_id.try_into().unwrap();
    //let season = Season::try_from(season_id)().unwrap(); // or this syntax
    println!("season: {season:?}");
}

Let's implement the enum_builder macro.

macro_rules! enum_builder {
    (
        // the target type to convert from/to. It must become at first before other meta tags
        #[repr($typ:ty)]

        // extra optional meta like #[derive(Debug)]
        $( #[$meta:meta] )*

        // accessor like pub or pub(crate)
        $access:vis enum $name:ident {
            // catch arms and assigned values like Sprint => 3
            // that is: $arm = Spring and $val = 3
            // I used literal not expr for later match expression.
            // If expr is used, you need to implement if/else instead of match.
            $( $arm:ident => $val:literal ),* $(,)?
        }
    ) => {
        // create the enum with specified arms without literal and values
        $( #[$meta] )*
        $access enum $name {
            $($arm,)*
        }

        // implement conversion from the target type to the enum.
        // Since the target type has a bigger domain, the conversion is fallible.
        // I mean, if target type be u8, there are 256 value for the type while or enum has much lesser arms, so
        // convesion from u8 to our enum may does not happen. 
        impl TryFrom<$typ> for $name {
            type Error = ();
            fn try_from(x: $typ) -> Result<Self, Self::Error> {
                match x {
                    $( $val => Ok($name::$arm),)*
                    _ => Err(()),
                }
            }
        }

        // implement convesion into target type. It always happens since the enum's arms are much lesser in count.
        impl From<$name> for $typ {
            fn from(enm: $name) -> Self {
                match enm {
                    $($name::$arm => $val ,)*
                }
            }
        }
    };
}

Online playground

This macro is a customized version of the original macro implemented in the rustls crate. The unknown arm is removed, and conversion from the target type is fallible here.

Conclusion:

Although there is no straightforward native solution to convert enums to and from integer values, we can utilize macros to implement a performant solution.