1

In this below example (here is the Rust playground), I need to filter values in an iterator that match a specific pattern.

One way I found is to use a match returning an Option in filter_map:

#[derive(Copy, Clone, Debug)]
struct Entity(i32);

enum Event {
    EntityInserted(Entity),
    EntityRemoved(Entity),
}

fn main() {
    let [entity1, entity2] = [Entity(1), Entity(2)];

    let events = vec![
        Event::EntityInserted(entity1),
        Event::EntityRemoved(entity1),
        Event::EntityInserted(entity2),
    ];

    let inserted_entities: Vec<_> = events
        .iter()
        .filter_map(|event| match event {                  // <--- 
            Event::EntityInserted(entity) => Some(entity), // <--- Those lines
            _ => None,                                     // <---    here
        })                                                 // <---
        .collect();

    dbg!(inserted_entities);
}

Is there a more idiomatic way to have this behavior ?

Didi Bear
  • 356
  • 3
  • 13
  • Does https://stackoverflow.com/questions/30467085/how-to-iterate-over-and-filter-an-array answer your question? – asky Aug 29 '20 at 06:36
  • 1
    If you want to filter *and unwrap* (aka return entities) then this looks idiomatic (clippy certainly doesn't complain about it). If it's a common task, then you just extract that as methods on `Event`, similar to [`Result::ok`](https://doc.rust-lang.org/std/result/enum.Result.html#method.ok) / [`Result::err`](https://doc.rust-lang.org/std/result/enum.Result.html#method.err) but that's about it. Also for such a simple pattern `if let` can be shorter and clearer than a full-blown `match`, but that's more of a personal preference really. – Masklinn Aug 29 '20 at 10:57
  • @Masklinn Thanks, having something like `Result::ok` is what I was looking for :) – Didi Bear Aug 29 '20 at 14:27

1 Answers1

1

As for the filter_map, I think that's the way to go.

Curiously, I wrote a macro yesterday that generates a Some in case the if was "successful", and otherwise evaluates to None:

macro_rules! if_then_some {
    ($cond: expr, $val: expr) => {
        if $cond { Some($val) } else { None }
    };
    (let $pattern:pat = $expr: expr, $val: expr) => {
        if let $pattern = $expr { Some($val) } else { None }
    };
}

As it stands, it can be used for checking bool-conditions and if let-conditions. In your case it could be used like this:

let inserted_entities: Vec<_> = events
    .iter()
    .filter_map(|event|
        if_then_some!(let Event::EntityInserted(entity)=event, entity)
    )
    .collect();

It is disputable if this is "idiomatic", but imho it is terse yet quite readable.

A bit off-topic: On the other hand, whenever I see if let with something else than Option or Result, I am wary that the compiler does not warn me if I add new variants that should be checked in these conditions.

phimuemue
  • 34,669
  • 9
  • 84
  • 115
  • Thanks for your answer :) While I personally do not think the suggested macro is the most appropriate solution, it shows well how the language benefits from adding syntactic sugar macro around pattern matching like [the `matches!` macro](https://doc.rust-lang.org/beta/std/macro.matches.html) – Didi Bear Aug 31 '20 at 02:46