rustenumsmacrospattern-matching

Matching enum variant without specifying variants in Rust macro


I am trying to use enum variants to store variables specific to tabs in an app. I am using the below code to extract the variable out of the enum. (because accessing a variant directly doesn't work, for example self.selection doesn't work)

enum Tab {
    StrokeProperties { selection: [bool; 2] },
    Canvas {},
    Library {},
}
let selection = match self { // `self` refers to `Tab`
    Tab::StrokeProperties { selection, .. } => { selection }
    _ => { !unreachable!() }
}

I want to use a macro to avoid having to write it every time. However it doesn't seem to work.

macro_rules! enum_get {
    ($matched_variant:expr, $matched_enum:expr, $field:ident) => {
        match $matched_variant {
            $matched_enum { $field, .. } => { $field },
            _ => { !unreachable!() }
        }
    }
}
...
enum_get!(self, Tab::StrokeProperties, selection); // Ideally I would also be able to remove the reference to `Tab::StrokeProperties` entirely, but I don't understand macros well enough to do that.
  --> src\ui.rs:9:27
   |
9  |             $matched_enum { $field, .. } => { $field },
   |                           ^ expected one of `...`, `..=`, `..`, `=>`, `if`, or `|`
...
65 |                             let selection = enum_get!(self, Tab::StrokeProperties, selection);
   |                                             ------------------------------------------------- in this macro invocation
   |
   = note: this error originates in the macro `enum_get` (in Nightly builds, run with -Z macro-backtrace for more info)

I managed to get it working kind of, with the following code. But it isn't a nice macro, and barely cuts down on code.

macro_rules! enum_get {
    ($matched_variant:expr, $matched_enum:pat, $field:ident) => {
        match $matched_variant {
            $matched_enum => { $field },
            _ => { !unreachable!() }
        }
    }
}
...
enum_get!(self, Tab::StrokeProperties { selection, .. }, selection);

Is it possible to make it work without the curly brackets ({ selection, .. }) in the macro call or completely remove Tab::StrokeProperties from the macro call?


Solution

  • To answer your first question, it is possible to remove the curly bracket section. It requires matching the enum variant (Tab::StrokeProperties) as a path not an expr or complete pattern. Documentation of available macro_rules! argument types can be found here. Here is the working code:

    enum Tab {
        StrokeProperties { selection: [bool; 2] },
        Canvas {},
        Library {},
    }
    
    macro_rules! enum_get {
        ($matched_variant:expr, $matched_enum:path, $field:ident) => {
            match $matched_variant {
                $matched_enum { $field, .. }=> { $field },
                _ => { unreachable!() }
            }
        }
    }
    
    fn main() {
        let tab = Tab::StrokeProperties{selection: [false; 2]};
        let selection = enum_get!(tab, Tab::StrokeProperties, selection);
        println!("{selection:?}"); // [false, false]
    }
    

    To answer your second question, it is not possible to remove Tab::StrokeProperties entirely, because the macro has no way of knowing which enum variants/fields exist, unless you made a more complex macro that consumed the entire enum Tab { ... } item.