Search code examples
genericsrustmetaprogramming

How to delegate pattern matching to a function in Rust?


I have a type like this:

#[derive(PartialEq, Eq, Debug, Clone)]
enum MyEnum {
    ValueOne,
    ValueTwo,
    Integer(i32),
    Text(String),
}

In my code I have a lot of similar patterns:

let value = match iterator.next() {
    Some(MyEnum::ValueOne) => MyEnum::ValueOne,
    Some(value) => return Err(format!("Unexpected value {:?}", value)),
    None => return Err("Unexpected end of input!"),
}

Or this:

let value = match iterator.next() {
    Some(MyEnum::Integer(i)) => MyEnum::Integer(i),
    Some(value) => return Err(format!("Unexpected value {:?}", value)),
    None => return Err("Unexpected end of input!"),
}

I would like to create a generic function take_value, where I can specify the MyEnum type I need, and it returns the Result

I can only solve it with simple values like this:

fn take_value(iterator: &mut Iterator<MyEnum>, expected: MyEnum) -> Result<MyEnum, String> {
    match iterator.next() {
        Some(expected) => Ok(expected),
        Some(value) => Err(format!("Unexpected value {:?}", value)),
        None => Err("Unexpected end of input!"),
    }
}

It can be called like this: let value = take_value(iterator, MyEnum::ValueOne)?;

But how is it possible to modify this function, so it can be called for MyEnum::Integer, without specifying the integer value inside? Like take_value(iterator, MyEnum::Integer)


Solution

  • You can't do it with a function, but the macro below comes close to what you want. Because we have to differentiate between patterns :pat and expressions :expr you have to repeat that part.

    #[derive(PartialEq, Eq, Debug, Clone)]
    enum MyEnum {
        ValueOne,
        ValueTwo,
        Integer(i32),
        Text(String),
    }
    macro_rules! take_value {
        ($iterator:expr, $pattern:pat, $expr:expr) => {
            match $iterator.next() {
                Some($pattern) => Ok($expr),
                Some(value) => Err(format!("Unexpected value {:?}", value)),
                None => Err("Unexpected end of input!".to_string()),
            }
        }
    }
    
    fn main() {
        let mut it = [MyEnum::ValueOne, MyEnum::ValueTwo, MyEnum::Integer(5)].into_iter();
        dbg!(take_value!(&mut it, MyEnum::ValueOne, MyEnum::ValueOne));
        dbg!(take_value!(&mut it, MyEnum::ValueOne, MyEnum::ValueOne));
        dbg!(take_value!(&mut it, MyEnum::Integer(i), MyEnum::Integer(i)));
        dbg!(take_value!(&mut it, MyEnum::ValueOne, MyEnum::ValueOne));
    }
    

    outputs

    …
    src/main.rs:21] take_value!(& mut it, MyEnum :: ValueOne, MyEnum :: ValueOne) = Ok(
        ValueOne,
    )
    [src/main.rs:22] take_value!(& mut it, MyEnum :: ValueOne, MyEnum :: ValueOne) = Err(
        "Unexpected value ValueTwo",
    )
    [src/main.rs:23] take_value!(& mut it, MyEnum :: Integer(i), MyEnum :: Integer(i)) = Ok(
        Integer(
            5,
        ),
    )
    [src/main.rs:24] take_value!(& mut it, MyEnum :: ValueOne, MyEnum :: ValueOne) = Err(
        "Unexpected end of input!",
    )