How to flatten many nested layers of pattern matches?

Viewed 917

I have code that requires enums to be a certain type before continuing (and if they are not then it is an unrecoverable error). Expressing this with the if let pattern is leading to a lot of indentation and syntax noise. Here's an example:

enum Fruit {
    Apple(i32),
    Banana(i32, i32),
}

enum Veggie {
    Tomato(bool),
    Pepper(i32),
}

fn test() -> i32 {
    let fruit = fruit_producing_func();
    if let Fruit::Apple(x) = fruit {
        let veggie = veggie_producing_func(x);
        if let Veggie::Pepper(y) = veggie {
            y
        } else {
            panic!();
        }
    } else {
        panic!();
    }
}

In my code this nesting gets much larger than 2 layers. Is there a more concise way to express this which avoids creating extra blocks, or even the repetition of the panic! statements? Simply doing:

let Fruit::Apple(x) = fruit;

leads to an error because let bindings have to be exhaustive (I was hoping it would just panic on failed match).

2 Answers

You could flatten it out by using match instead:

fn test() -> i32 {
    let fruit = fruit_producing_func();
    let x = match fruit {
        Fruit::Apple(x) => x,
        _ => panic!(),
    };
    let veggie = veggie_producing_func(x);
    let y = match veggie {
        Veggie::Pepper(y) => y,
        _ => panic!(),
    };
    y
}

Alternatively you could implement the matching in an associated function and then process that result using usual Option idioms.

impl Fruit {
    fn apple(&self) -> Option<i32> {
        if let Fruit::Apple(x) = self {
            Some(*x)
        } else {
            None
        }
    }
}

impl Veggie {
    fn pepper(&self) -> Option<i32> {
        if let Veggie::Pepper(y) = self {
            Some(*y)
        } else {
            None
        }
    }
}
fn test() -> i32 {
    let fruit = fruit_producing_func();
    let x =  fruit.apple().unwrap();
    let veggie = veggie_producing_func(x);
    veggie.pepper().expect("Tomato is a fruit after all")
}

You can refactor the checks into convenience methods on the enums themselves, then that will flatten your test function:

enum Fruit {
    Apple(i32),
    Banana(i32, i32),
}

impl Fruit {
    // panics if not apple
    fn get_apple(&self) -> i32 {
        if let Fruit::Apple(x) = self {
            *x
        } else {
            panic!()
        }
    }
}

enum Veggie {
    Tomato(bool),
    Pepper(i32),
}

impl Veggie {
    // panics if not pepper
    fn get_pepper(&self) -> i32 {
        if let Veggie::Pepper(y) = self {
            *y
        } else {
            panic!()
        }
    }
}

fn fruit_producing_func() -> Fruit {
    todo!()
}

fn veggie_producing_func(x: i32) -> Veggie {
    todo!()
}

fn test() -> i32 {
    let fruit = fruit_producing_func();
    let x = fruit.get_apple();
    let veggie = veggie_producing_func(x);
    let y = veggie.get_pepper();
    y
}

playground

Related