简体   繁体   中英

Matching tuple-like enum variants in a macro, where enum type and variants are metavariables: how do I write the matching pattern?

Concisely put into Rust code, I'm trying to generate a pattern match like this:

if let Foo::Variant(_) = value {}
//     ^^^^^^^^^^^^^^^

in a macro, with both Foo (a type) and Variant (an identifier) passed to the macro as metavariables. In the real use case, I'm generating a match instead of an if let and am using multiple variants of the same enum, but if let resulted in a shorter reproducible example.

This works with simple enums:

enum Foo {
    Variant,
}

macro_rules! match_enum {
    (
        $value:ident: <$enum:ty>::$variant:ident
    ) => {
        if let <$enum>::$variant = $value {}
    };
}

fn main() {
    let foo = Foo::Variant;
    match_enum!(foo: <Foo>::Variant);
}

This compiles.

However, when I make the enum variant tuple-like, it breaks (changes highlighted):

enum Foo {
    Variant(usize),
//         ^^^^^^^
}

macro_rules! match_enum {
    (
        $value:ident: <$enum:ty>::$variant:ident
    ) => {
        if let <$enum>::$variant(_) = $value {}
//                              ^^^
    };
}

fn main() {
    let foo = Foo::Variant(0);
//                        ^^^
    match_enum!(foo: <Foo>::Variant);
}
   |         if let <$enum>::$variant(_) = $value {}
   |                -----------------^^^ unexpected `(` after qualified path
   |                |
   |                the qualified path
...
   |     match_enum!(foo: <Foo>::Variant);
   |     --------------------------------- in this macro invocation

I have tried some variations, more or less blindly; $enum::$variant(_) , <$enum::$variant>(_) , <$enum::$variant>::(_) among them.

Is this possible? Am I perhaps using the wrong types of metavariables?

This question seems to be related, but it focuses on mixing unit and tuple variants, and has not been resolved.

The problem seems to be caused by the $enum metavariable, as the following slight modifications show:

macro_rules! match_enum {
    (
        $value:ident: <$enum:ty>::$variant:ident
    ) => {
        // does not fix the problem
        if let <$enum>::Variant(_) = $value {}
        // fixes the problem
        if let Bar::$variant(_) = $value {}
    };
}

As the problem happens on the syntax level, we can try change the syntactic makeup of the generated code, in particular by introducing a type alias. We then need to scope that type to not leak out of the macro:

macro_rules! match_enum {
    (
        $value:ident: <$enum:ty>::$variant:ident
    ) => {
        {
            type Enum = $enum;
            if let Enum::Variant(_) = $value {}
        }
    };
}

It's just a workaround, but it's clean enough.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM