Cleanly iterating over a vector of mixed enum variants

134 Views Asked by At

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.

2

There are 2 best solutions below

1
cafce25 On BEST ANSWER

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)+
                }
            }
        }
    };
}
0
val On

I may be misunderstanding, but you can probably hide that with one or more callbacks ?

i.e.:

fn do_something<T, F, H>(f: F, h: H)
    where F: Fn(T) -> (),
          H: Fn(SomeMessageType) -> ()
{
    let data = ...;
    data.for_each(|either| {
        match either {
            Either::Left(t) => f(t),
            Either::Right(r) => h(r),
        }
    })
}

Which you can then call with whichever handler you want for both types:

do_something(
    |t| { match t { MyEnum::Some => ... }}, 
    |msg| { println!("I received this message: {}", msg); }
);

Would that solve your problem, as the Either implementation is hidden in do_something