How to match multiple cases?

Hi, experts. What is the equivalent in Rust to the following code?

switch(bits_per_sample) {
    case  8:
    case 16:
    case 32: foo(); break;
    case 12: bar(); break;
    case 24: baz(); break;
    default: break;
}

The following works. But I suppose it is not the recommended way, is it?

match bits_per_sample {
    8  => foo(),
    16 => foo(),
    32 => foo(),
    12 => bar(),
    24 => baz(),
    _  => (),
};

Thanks in advance.

1 Like

You can or several patterns in a match:

match bits_per_sample {
    8 | 16 | 32 => foo(),
    12 => bar(),
    24 => baz(),
    _ => (),
};

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=5cc6ba9df79ac049c050b246dfca8e53

6 Likes

Great. I did not know the | operator.

What if there are lots cases to match? For example:

    let status_foo = [1, 2, 4, 11, 27, 88];
    let status_bar = [3, 5, 6, 7, 8, 9];
    let status = 2;
    match status {
        status_foo ?? => foo(),
        status_bar ?? => bar(),
        _ => (),
    };

I would like to re-use the status_foo and status_bar. The status is hard-coded from the manual so I cannot change them.

Thank you.

There are several ways to re-use the hardcoded lists of numbers, and defining a local variable containing them as an array probably isn't the best one.

As a learning exercise, you can use guards in this instance:

let status_foo = [1, 2, 4, 11, 27, 88];
let status_bar = [3, 5, 6, 7, 8, 9];
let status = 2;
match status {
    status if status_foo.contains(&status) => foo(),
    status if status_bar.contains(&status) => bar(),
    _ => (),
};
1 Like

If you would like to compare against a constant list of possibilities without writing them directly into the match, it is possible to do this efficiently — by constructing a lookup table at compile time. This code has the same effect as tuffy's above, but avoids scanning the lists every time the match is performed.

It's a little verbose, because const evaluation is currently limited and can't use for loops or traits, but straightforward otherwise. I've used u8 as the data type to compare to allow the lookup table to be very small and yet not need any bounds check; if your real values don't fit u8 then it might be possible to adjust that, or it might be better to use a different strategy.

I'm not recommending you use this code, necessarily; I thought it might be useful to know about further possibilities.

// The raw original lists, used to construct the lookup table
const STATUS_FOO: &[u8] = &[1, 2, 4, 11, 27, 88];
const STATUS_BAR: &[u8] = &[3, 5, 6, 7, 8, 9];

/// An enum with one variant per possible outcome.
#[derive(Copy, Clone, Debug, PartialEq)]
enum Category {
    Foo,
    Bar,
    Other,
}

// The lookup table from integer to Category, computed from STATUS_FOO and STATUS_BAR.
const STATUS_LOOKUP: [Category; 256] = {
    const fn contains(table: &[u8], item: u8) -> bool {
        let mut j = 0;
        while j < table.len() {
            if table[j] == item {
                return true;
            }
            j += 1;
        }
        false
    }

    let mut table = [Category::Other; 256];
    // Constant evaluation doesn't yet support for loops
    let mut i = 0;
    while i < table.len() {
        let value = i as _;
        table[i] = if contains(STATUS_FOO, value) {
            Category::Foo
        } else if contains(STATUS_BAR, value) {
            Category::Bar
        } else {
            Category::Other
        };
        i += 1;
    }
    table
};

// Example usage

fn foo() {
    println!("foo");
}
fn bar() {
    println!("bar");
}
fn main() {
    let status: u8 = 2;
    match STATUS_LOOKUP[status as usize] {
        Category::Foo => foo(),
        Category::Bar => bar(),
        Category::Other => (),
    };
}
5 Likes

That works. Thank you.

It is beyond my understanding why it is limited. Any explanation?
That constucting a lookup table at compile time is really interesting. Thank you so much.

Trait impls cannot currently be marked const, so any iterator might use runtime-only features, and for loops desugar to use IntoIterator and Iterator.

There is ongoing work to define how const traits ought to behave; this internals thread is a relatively recent development with reference to some prior work if you are interested in learning more.

Be aware const fn itself is only a couple years old, and when first introduced it was far more limited than it is today. const evaluation has gotten much more powerful since 2018 but there's still work to be done and const traits are probably the biggest piece of that. So, you know, be patient :slight_smile:

Thanks. That must be for experts only :alien: