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.
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.
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 filter
int 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 };
/* .... */
}
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.
… and by the way, welcome to this forum!
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 };
/* .... */
}
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;
};
/* .... */
}
an equivalent alternative would use match
for e in myenum_vector {
let field = match e {
MyEnum::Variant { field } => field,
_ => continue,
};
/* .... */
}
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;
}
}
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,
}
}
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
.
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)
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} ");
}
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.)
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. ↩︎
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.
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))
}
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.
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 }
)}
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 match
es 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.
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.
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)
{
// ...
}
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(_)))
{
/* …… */
}
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.