Macros, How to generate enum variant from str literal?

476 Views Asked by At

I have an enum with over 100 variants. and I have to get each of its variants from a string. Something like this.

enum VeryLongEnum {
    A,
    B,
    C,
    D,
    E,
}

impl From<&'static str > for VeryLongEnum {
    fn from(s: &'static str) -> VeryLongEnum {
        match s {
            "A" => VeryLongEnum::A,
            "B" => VeryLongEnum::B,
            "C" => VeryLongEnum::C,
            "D" => VeryLongEnum::D,
            "E" => VeryLongEnum::E,
        }
    }
}

But I don't want to write all the variants one by one, it's crazy.

I'm trying to create a mini macro to simplify this, I did something like this.

macro_rules! from_str {
    ( $s:expr ) => {{
        let t: VeryLongEnum = VeryLongEnum::$s;
        t
    }};
}

to use like this.

let variant = "A";
let en = from_str!(variant);

But I'm having an error which says expected a identifier.

I understand that identifiers and expresions are different types of token trees, but the question is how can I "force" this to generate the enum variant with the literal?

1

There are 1 best solutions below

1
On BEST ANSWER
let variant = "A";
let en = from_str!(variant);

variant is a string that exists at runtime, you cannot pass it to a macro like that.

An alternative is to define a macro that defines the enum as well as the string conversion. This macro can use the stringify! macro in Rust to convert at identifier to a static string that can be passed to pattern match. Since the conversion is fallible, you should define a TryFrom instead of From (or FromStr which allows you to call "A".parse()).

macro_rules! go {
    ($($ident:ident)+) => {
        #[derive(Debug)]
        enum VeryLongEnum {
            $($ident,)+
        }

        impl TryFrom<&'static str> for VeryLongEnum {
            type Error = &'static str;

            fn try_from(s: &'static str) -> Result<VeryLongEnum, &'static str> {
                match s {
                    $(stringify!($ident) => Ok(VeryLongEnum::$ident),)+
                    _ => Err("Invalid String")
                }
            }
        }
    }
}

go! { A B C D E }

fn main() {
    let _ = dbg!(VeryLongEnum::try_from("A"));
    let _ = dbg!(VeryLongEnum::try_from("E"));
    let _ = dbg!(VeryLongEnum::try_from("F"));
}

Output:

[src/main.rs:24] VeryLongEnum::try_from("A") = Ok(
    A,
)
[src/main.rs:25] VeryLongEnum::try_from("E") = Ok(
    E,
)
[src/main.rs:26] VeryLongEnum::try_from("F") = Err(
    "Invalid String",
)

Playground