user7876637
user7876637

Reputation: 154

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.

Upvotes: 0

Views: 201

Answers (2)

val
val

Reputation: 8689

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

Upvotes: 0

cafce25
cafce25

Reputation: 27549

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

Upvotes: 1

Related Questions