Search code examples
rustrust-macrosmacro-rules

Macros, How to generate enum variant from str literal?


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?


Solution

  • 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