Pattern match in 'for'

I want to use pattern match in the for statement like:

for MyEnum { field } in myenum_vector {
   ....
}

If it doesn't match, skip it or panic.

1 Like

One would have to decide which one in particular should happen.

Implicit panicking on mis-match of a pattern is something Rust generally avoids though. (In particular when compared to other languages such as Haskell.)

Skipping elements that don’t match does also seem somewhat surprising to me though; essentially you’re logically filterint the iterator then, or using continue without writing it, better to be explicit. It’s not even all that bad, especially once we have the (still unstable) let_else feature:

for e in myenum_vector {
    let MyEnum::Variant { field } = e else { continue };
    /* .... */
}

Rust Playground

Here the use of continue is explicit; you could also, alternatively, choose to use panic!() instead; the only overhead is the need to introduce a temporary name for the item, but that name can be kept fairly short as it’s only used once in the very next line anyways.

18 Likes

… and by the way, welcome to this forum! :wave:

For comparison, I’ll quickly also elaborate some more on the stable alternatives to the code using let_else above.


One can “replace” the let … else line directly with a sort-of equivalent using if let

for e in myenum_vector {
    let field = if let MyEnum::Variant { field } = e { field } else { continue };
    /* .... */
}

Rust Playground

which has the downside that you need to write field quite often (and it becomes worse with more fields, involving tuples). The let in this approach would also be formatted by rustfmt to a lengthy 4-liner

for e in myenum_vector {
    let field = if let MyEnum::Variant { field } = e {
        field
    } else {
        continue;
    };
    /* .... */
}

Rust Playground

an equivalent alternative would use match

for e in myenum_vector {
    let field = match e {
        MyEnum::Variant { field } => field,
        _ => continue,
    };
    /* .... */
}

Rust Playground

saving a line in formatted code, but with all the same downsides otherwise.


Alternatively, one can restructure the code, and integrate all the /* ... */ part into an if let

for e in myenum_vector {
    if let MyEnum::Variant { field } = e {
        /* .... */
    } else {
        continue;
    }
}

Rust Playground

This has the downside that all the code in /* ... */ gains an additional level of indentation, and also that the continue is at the very end instead of the beginning of the code. Using match would be even worse, as it adds two layers of indentation (and also has the continue in the end):

for e in myenum_vector {
    match e {
        MyEnum::Variant { field } => {
            /* .... */
        }
        _ => continue,
    }
}

Rust Playground

4 Likes

The problem with allowing refutable pattern matching in for loops is that one could reasonably think that it means either "take the iterator elements while they match this pattern" or "filter the iterator elements that match this pattern".

So this:

for (x, 0) in [(1, 0), (2, 0), (3, 1), (4, 2), (5, 0)] {
    print!("{x} ");
}

could either print 1 2 or 1 2 5.

9 Likes

Incidentally, this would simply correspond to a choice between continue and break in the code example with let_else that I provided above. Or, applied to your code example,

for i in [(1, 0), (2, 0), (3, 1), (4, 2), (5, 0)] {
    let (x, 0) = i else { continue };
    print!("{x} ");
}

vs

for i in [(1, 0), (2, 0), (3, 1), (4, 2), (5, 0)] {
    let (x, 0) = i else { break };
    print!("{x} ");
}

(both examples in the playground)

2 Likes

This version in particular can also be written

let mut it = [(1, 0), (2, 0), (3, 1), (4, 2), (5, 0)].into_iter();
while let Some((x, 0)) = it.next() {
    print!("{x} ");
}
3 Likes

Interestingly, this desugaring convinces me that the take-while approach would be the most "natural" for for, since the for desugaring looks basically like this currently (with the added restriction of the pattern being exhaustive, of course).

However, even while this would be the "natural" result of the "obvious" desugaring of for, I think I agree that this is far too subtle for language sugar, and is better served by writing the let $pat else break;.

(I also think that else if could and maybe should be generalized to support else $keyword[1], but I freely admit that I'm in the minority there. Disclaimer: this is my own opinion and does not represent that of wg-grammar.)


  1. Useful else $keyword candidates: break, continue, return, yeet, if (obviously), match, try(?), const(?), loop(?), unsafe(?)... basically all expression introducing keywords other than async/unsafe seem at least plausibly reasonable to use, and even those aren't terrible. Full expression syntax, even with a no-brackets restriction like in if condition position, though, is a bit too much. ↩︎

3 Likes

Thx for your responses which make sense to me.

If we ever get types for enum variants (which I would like to), this could be

for MyEnum::Variant { field } in myenum_vector.into_iter().filter_map(|e| match e { MyEnum::Variant { .. } => Some(e), _ => None }) { ... }

And maybe even write some generic function for that.

You can do something similar even today, though.

1 Like

What's wrong with doing just this?

for e in myenum_vector {
    if let MyEnum::Variant { field } = e {
        /* .... */
    }
}

It is explicit and not much longer than the statement OP wants. If one desperately needs the short version, macro_rules is always there to help.

enum E<'b> {
    A(u32),
    B(&'b str),
}

macro_rules! for_match {
    ($pat: pat in $expr: expr => $code: expr) => {
        for e in $expr {
            if let $pat = e {
                $code
            }
        }
    }
}

fn main() {
    let x = vec![E::A(1), E::B("x")];
    for_match!(E::A(a) in x => println!("a: {}", a))
}

Rust Playground

Blocks work too, as they are, indeed, expressions:

for_match!(E::A(a) in x => {
    println!("a: {}", a);
})

Not as elegant, though as short and intuitive. As for what to do on mismatch, that's for the macro author to decide.

1 Like

It's cool, thank you.

I think the applicable sugar here, especially after let else, would be:

for MyEnum { field } else { continue } in my_enum_vector {
// or
for MyEnum { field } else { panic!() } in my_enum_vector {

or maybe the else would be trailing:


for MyEnum { field } in my_enum_vector else { continue } {
// or
for MyEnum { field } in my_enum_vector else { panic!() } {

More generally, reducing one level of indentation by merging a for and a match would be quite convenient and still very general-purpose:

for match in my_enum_vector {
    MyEnum { field } => {
        /* loop body */
    },
    _ => continue /* or panic!() */,
}

Those are just ideas regarding how to slightly improve the syntax of these patterns. Note that this last one is definitely within reach of a macro_rules! macro:

for_match!(in my_enum_vector, {
    MyEnum … => …,
    … => …,
});

with

macro_rules! for_match {( in $iterable:expr, $body:tt ) => (
    for elem in $iterable { match elem $body }
)}
1 Like

One could generalize this syntax to allow match as a pattern in a few language constructs that only allow irrefutable patterns and (can) have a body. E.g. also

for (i, match) in my_vector.iter().enumerate() {
    MyEnum { field } => {
        println!("{}", i);
    }
    _ => continue,
}

or notably in closures

my_vector.iter().for_each(|match| {
    MyEnum { field } => {
        println!("hello");
    }
    _ => {}
});

or in function definitions

fn process_my_enum(match: MyEnumType) {
    MyEnum { field } => {
        /* …… */
    }
    MyOtherEnumVariant { foo } => {
        /* …… */
    }
    _ => panic!(),
}

Of course, the question with such a generalization is how to handle multiple matches in the same pattern. If we allowed multi-value match (without tuples) anyways like

match foo(), bar() {
     FooPattern, OtherPattern(bar_field) => { /* …… */ }
     _, _ => /* … */
}

then I’d say, something like

fn xor(match: bool, match: bool) {
    false, false => false,
    false, true => true,
    true, false => true,
    true, true => false,
}

ought to work. OTOH, then there’s no good way to support OR-patterns here across the multiple values [1], unless we’d have | take lower precedence than the , (because you cannot exactly parenthesize expr, expr without turning it into a tuple). But AFAIK | takes higher precedence in tuple / tuple struct patterns already, so that would be somewhat inconsistent.


  1. i.e. in a way that the above xor could be written with just 2 arms ↩︎

Can you explain how your desired result would be different from the following?

for field in myenum_vector.filter_map(|e| if let MyEnum {field} = e { Some(field) } else { None} ) {
   ....
}

If you want to change the skip to panic you can change the filter_map to a map and the None to a panic!.

There are many ways to solve the problem, but I want a more convenient usage. Actually, in my scenes, all elements in the vector are the same because I've filtered them already. So if in this situation, I think

for MyEnum(MyStruct { .. }) in filtered_enum_vector {
}

is more convenient. But the problem is we cannot write like this in the most situations when the vector isn't filtered before. I want an applicable sugar like @dhm supplied indeed.

Then perhaps what you actually want might be enum variant types so that your filtered list can actually know which enum variant it is.

In general, Rust encourages you to parse, not validate, so you'll generally have a better time if you filter_map your vector rather than just filter it.

5 Likes

The extract! macro in the enum_extract crate (or other similar macros like try_match) might come in handy. It's basically a destructuring version of matches! and I'd like to see something like it in std someday:

for field in myenum_vec
    .into_iter()
    .filter_map(e -> extract!(MyEnum::Variant { _ }, e) 
{
    // ...
}
1 Like

Arguably, that macro can easily be expanded to be able to produce a closure, too:

for field in myenum_vec
    .into_iter()
    .filter_map(extract!(MyEnum::Variant(_)))
{
    /* …… */
}

Rust Playground

2 Likes

Hm, that's an interesting idea. I'm not sure how I feel about it. I created the extract_macro crate for pulling a value out of a pattern, but adding _ feels a bit too much.

In case that wasn’t clear, note that that _ syntax is not mine, I’ve merely added the final 2 cases in the macro definition to produce closures, turning extract!(…) into |e| extract!(…, e), to the existing macro.

By the way, this extension was only straightforward because that macro chose to put the expression being matched on at the end; otherwise it seems hard to distinguish the cases. Also, it appears to me that the support for being implicit about what’s being returned has, at least in the case of that enum_extract crate, the downside that it doesn’t support any more complex patterns, and also you cannot choose which fields you want to keep or skip with tuple-style variants, AFAICT.