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 inrustls
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}");
}
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.
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:?}");
}
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 ,)*
}
}
}
};
}
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.
Top comments (0)