DEV Community

Raeisi
Raeisi

Posted on • Edited on

Converting enums to and from integers in Rust, implementing the rustls solution

Rust has some cool features, especially its enum types. They’re like a super-powered way to store different types of data. You can use them to store simple numbers or even complex structures, depending on what you need. Enums also have their own little language, which can be a bit tricky for beginners, but I’ve got some ideas on how to make it easier.

See full code example at Rust Online Playground

This document outlines my plan for enum_builder! macro in rustls crate.

Why Enum Conversion?

Enum conversion is a crucial step when you’re dealing with objects that need to be packed or sent over the wire. It usually involves changing enums into integers, which happens in places like databases, APIs, and even raw data.

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}");
}
Enter fullscreen mode Exit fullscreen mode
error[E0308]: mismatched types
 --> src/main.rs:8:25
  |
8 |     let season_id: u8 = Season::Autumn;
  |                    --   ^^^^^^^^^^^^^^ expected `u8`, found `Season`
  |                    |
  |                    expected due to this
Enter fullscreen mode Exit fullscreen mode

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 _;
}
Enter fullscreen mode Exit fullscreen mode

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

Why macro?

Rust enums, which are great for type safety, don’t automatically convert to integers. But there’s a cool macro called enum_builder! that can help you out. It automatically generates into() and from() functions for your enum. This macro takes the name of your enum and a list of its variant-integer pairs. It then creates match expressions inside the impl block for conversion, using the From trait. This way, you don’t have to write out the conversion logic yourself. It saves you time, makes your code shorter, and reduces errors. Plus, it’s easier to maintain because all the conversion logic is in one place. The example uses u8, but the macro can be used for other integer types too. So, if you need to convert enums to integers in Rust, this macro is your friend!

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:?}");
}
Enter fullscreen mode Exit fullscreen mode

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 ,)*
                }
            }
        }
    };
}
Enter fullscreen mode Exit fullscreen mode

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.

Playwright CLI Flags Tutorial

5 Playwright CLI Flags That Will Transform Your Testing Workflow

  • 0:56 --last-failed
  • 2:34 --only-changed
  • 4:27 --repeat-each
  • 5:15 --forbid-only
  • 5:51 --ui --headed --workers 1

Learn how these powerful command-line options can save you time, strengthen your test suite, and streamline your Playwright testing experience. Click on any timestamp above to jump directly to that section in the tutorial!

Watch Full Video 📹️

Top comments (0)

The Most Contextual AI Development Assistant

Pieces.app image

Our centralized storage agent works on-device, unifying various developer tools to proactively capture and enrich useful materials, streamline collaboration, and solve complex problems through a contextual understanding of your unique workflow.

👥 Ideal for solo developers, teams, and cross-company projects

Learn more

👋 Kindness is contagious

If this article connected with you, consider tapping ❤️ or leaving a brief comment to share your thoughts!

Okay