vectorrustenumsmacroseither

Cleanly iterating over a vector of mixed enum variants


I have code like the following:

enum Either<L, R> {
    Left(L),
    Right(R)
}

enum SomeMessageType {
    // ...
}


fn do_something<T>() {
    let data: Vec<Either<T, SomeMessageType>> = ...;

    // ...
}

I want to be able to iterate over the contents of data without explicitly specifying the Either types, because having to specify the Either everywhere makes the API ugly and annoying to work with. The generic type T in do_something will always be an enum variant, I just don't know if it's possible to express that in Rust's generic types. Ideally, I'd like to be able to write something like:

fn do_something<...>() {
    let data = ...;
    matching_iterator!(data, {
        SomeMessageType::... => ...
        T::SomeVariant => ...
    });
}

I have tried writing a macro like this already:

#[macro_export]
macro_rules! check_mail {
    ( $data:expr, { $( $pattern:pat => $handler:block )+ } ) => {
        {
            use $crate::build_pattern;
                for data in $data.iter() {
                    if let $crate::build_pattern!($( $pattern )+) = message {
                        $( $handler )+
                    }
                }
            }
        }
    };
}

#[macro_export]
macro_rules! build_pattern {
    ( $pattern:pat ) => {
        Either::Right($pattern)
    };

    ( $pattern:pat ) => {
        Either::Left($pattern)
    };
}

but obviously this code won't compile, much less run. My intuition says I should put a differentiator of some sort at the start of each pattern, to make it easier to write the macro, but I haven't been able to get that to work. Each attempt generates the match arm code wrong, with all the matches at the start and then all the handler blocks at the end, and I'm not sure why.


Solution

  • You'd have to have some way to differentiate Left and Right patterns I used ; here.

    Also you can't match on variants of a generic cause it might be any type and might not even have them.

    fn do_something() {
        use Either::*;
        let data = vec![Right(T::SomeVariant), Left(SomeMessageType::Good)];
        matching_iterator!(data,
            SomeMessageType::Good => {}
            ;
            T::SomeVariant => {}
            T::SomeOtherVariant => {}
        );
    }
    
    #[macro_export]
    macro_rules! matching_iterator {
        ( $data:expr, $($lpattern:pat => $lhandler:block)+; $($rpattern:pat => $rhandler:block)+ ) => {
            {
                for data in $data.iter() {
                    match data {
                        $(Either::Left($lpattern) => $lhandler)+
                        $(Either::Right($rpattern) => $rhandler)+
                    }
                }
            }
        };
    }