Iterating over vector of Results (of tuples) – idiomatic way

Hi, I am new to Rust and as I have a Python background, Rust gives me hard time often.

I desperately looking for help with iterating over a vector of results as I cannot wrap my head around it. I read the Rust Book, googled the problem, searched on forum and youtube, but still cannot find the answer. I spent three evenings on the problem, came up with working solutions, but they do not look idiomatic/good-practice (I have no Rust experience) and I cannot find a good example of solving my problem. Moreover, I found the solution more as a trial-error way, thus, I do not really understand why these solution works and not the other I tried.

Problem statement:
I am learning by doing a project and I am using an external function that returns Vec<Result<(Vec<u8>, u8)>, _> I want to iterate over it and filter based on the second value of each tuple.

Example 1
Here is a simpler toy example without tuples.

let vec = vec![Ok(0), Ok(1), Err(2)];
let res: Vec<_> = vec.into_iter().filter(|t| {
    match t {
        Ok(val) =>  *val >= 1,
        Err(_) => false
    }
}).collect();
println!("{:?}", res);
// Returns: [Ok(1)] // Corect

Questions:

  1. Is using match in closure a good practice? Can I filter the vector elements based on their inner value (inside Result) in a simpler and more elegant way?
    I tried different approaches, but most of the time the compiler complains that the 1 should be of type Result not i32. I would like to avoid using any unwrap or other unsafe methods.

Example 2
Here same example but now the elements are tuples as in my project problem.

let vec = vec![Ok((0,1)), Ok((1,2)), Err((2,3))];
let res: Vec<_> = vec.into_iter().filter(|t| {
      let x = t.map(|(x1,x2)| x2);
      match x {
          Ok(val) =>  val >= 1,
          Err(_) => false
      }
  }).collect();
  println!("{:?}", res);
// Returns: [Ok((0,1)), Ok((1,2))] // Corect

Questions:

  1. If the Result contains a tuple, how to 'deconstruct' Result in Example 2 in idiomatic way, so that I can use the tuple inner elements for filtering? I don't like the filter part I came up with and the t.map() as it seems too complicated and 'spaghetti code' like.

TLDR
What I want to achieve?

  1. Iterate over a vector where each element is of type Result<(_, _), _> .
  2. Filter the vector based on the second element of each tuple (see Example 2)
  3. I am not interested in Errors, just omit them in the final collected results.
  4. res can be of type Vec<_> or Result<Vec<_>, _> for now I do not care (or should I?).

My main problems:

  1. If each element of a vector is a Result how to filter them based on the inner values? What is the more idiomatic (good-practice) way of doing so, but without using some unsafe methods such as unwrap etc?
  2. What is the best practice to get to the inner values of tuples wrapped in Results and then filter based on some element of each tuple? (Example 2)

I appreciate any help, idiomatic/good-practice solutions or some resource where I can learn more (but I tried the Rust book already and Rust by Example :anguished:)

Rust by example has a few general strategies that might help get you there: Iterating over Results - Rust By Example

1 Like

It's fine with you need it. Sometimes there are more concise ways such as the matches! macro. So here's an alternative to your first example:

    let res: Vec<_> = vec
        .into_iter()
        .filter(|t| matches!(t, Ok(i) if i >= &1))
        .collect();

But instead .into_iter() ... collect(), you can use Vec::retain:

    let mut vec = vec![Ok(0), Ok(1), Err(2)];
    vec.retain(|t| matches!(t, Ok(i) if i >= &1));
    println!("{:?}", vec);

Do you need to retain the Results inside the Vec? If not, you can filter them out in an iterator chain:

    let res: Vec<_> = vec
        .into_iter()
        // `Err(_)` will be filtered out and `Ok(i)` becomes `i`
        .filter_map(|t| t.ok())
        .filter(|i| i >= &1)
        .collect();

(We can't use retain here because we're changing the type within the Vec.)

This is one of the approaches on the page @scottmcm linked.

1 Like

I see nothing wrong with your code tbh. Maybe the introduction of the variable x in the second snippet is somewhat superfluous, but otherwise the code is perfectly fine.

I doubt that you will ever be able to find an exactly matching solution to a problem that is so specific and so low-level (i.e., it purely concerns what syntax to use in the context of results of tuples). You should read other experienced programmers' code a lot, and then based on that, formulate your own style that is not in contradiction with that of the rest of the ecosystem. That is probably the best way. And don't worry too much about what's "best" way while you are learning. If there is something grossly wrong with your code that you post here, people will notice and they will tell you.

1 Like

Thank you for your detailed response with some sample solutions and explanations.
You helped me a lot. I started to understand what is going on and what I should be paying attention to in this case.
matched! macro seems super useful in my case, also filter_map I finally understood after your example.

But I have the last question about filter_map. I wanted to filter on the second element of a tuple and return only this second element. What I understood from the docs filter_map expects Option to be returned, but in my example below, the filter_map seems to return i32 and not Option.

// // Filter on and keep second element
let vec = vec![Ok((0,1)), Ok((1,2)), Err((2,3))];
let res: Vec<i32> = vec
    .into_iter()
    .filter_map(|t| t.ok().map(|(_,j)| j)) // t.ok is an Option, but j not, why compalier does not complain?
    .filter(|j| j >= &1)
    .collect();
println!("{:?}", res);
// Returns [1,2] // Correct
t.ok() // turns the Result into a Option mapping Ok(...) to Some(...) and Err(...) to None.
    .map(|(_,j)| j)  // turns the Option<(i32, i32)> into a Option<i32> and this is what you return from your filter_map.
1 Like

The various iterators and error/none handling methods are definitely very tricky - particularly as there are lot of combinations, some of which are obscured by being specific trait implementations, and can generally be very hard things to search for.

I found this article really helpful for covering the various ways to handle "multiple results"

Based on one of the examples from that article, I think you want to use filter_map combined with Result::ok, because

  • Result::ok turns Ok(thing) to Some(thing) and Err(_) to None
  • filter_map removes None and keep the value from Some(value)

Thus you end up with just your "successful tuples", and the errors get quietly dropped.

fn main() {
    let vec = vec![Ok((0,1)), Ok((1,2)), Err((2,3))];

    // Collect successful results, throwing any errors away
    let results: Vec<(i64, i64)> = vec.into_iter().filter_map(Result::ok).collect();
    dbg!(&results);

    // Do some filtering with second element of tuple
    for (x, y) in results {
        if y > 1 {
            println!("Simple, as no results to worry about!");
        }
    }
}

The partition trick mentioned in the article would also be useful if you want to keep the Err tuples for other purposes.

Not quite what you describe, but much more often I want to bail out on the first error, for which the collect Vec<Result<x>> to Result<Vec<x>> trick is really useful,

fn main() {
    // Same as before (just with explicit types to simplify the hints for `collect` below)
    let vec: Vec<Result<(i64, i64), (i64, i64)>> = vec![Ok((0,1)), Ok((1,2))];

    // Collect results, bailing out if there is any errors
    let results: Result<Vec<_>, _> = vec.into_iter().collect();
    let results = results.unwrap(); // Crude error handling

    // Same as before
    for (x, y) in results {
        if y > 1 {
            println!("Simple, as no results to worry about!");
        }
    }
}

Probably worth mentioning also that although there is all the fancy iterator methods, a dumb old loop is often a perfectly valid and idiomatic approach. For example,

fn main() {
    let vec = vec![Ok((0,1)), Ok((1,2)), Err((2,3))];

    for item in vec {
        if let Ok((x, y)) = item {
            if y > 1 {
                println!("Found a result with second tuple value over 1!");
            }
        }
    }
}

It might end up longer, but idiomatic Rust code doesn't mean "short" :grin:

2 Likes

Ah just saw this while writing above. I think as soon as you start doing anything "non trivial" things with the iterated values, a simple for loop often becomes the neatest way.

By that I mean, I think this is the most concice way you can write it with iterators:

    let vec = vec![Ok((0,1)), Ok((1,2)), Err((2,3))];

    let good_values: Vec<i32> = vec
        .into_iter()
        .filter_map(Result::ok)
        .filter(|x| x.1 > 1)
        .map(|x| x.1)
        .collect();

    dbg!(&good_values);

..whereas writing it with a for loop and a let binding works out to be, arguably, shorter, and I'd also argue easier to understand (e.g it makes it clear the Err variants are silently dropped because there's no else arm on the if let Ok(...))

    let vec = vec![Ok((0,1)), Ok((1,2)), Err((2,3))];

    let mut good_values = vec![];
    for item in vec {
        if let Ok(x) = item {
            if x.1 > 1 {
                good_values.push(x.1);
            }
        }
    }

    dbg!(&good_values);
1 Like

also flatten works here:

let res: Vec<_> = vec.into_iter().flatten().filter(|i| i >= &1).collect();
1 Like

Thank you everyone for solutions, examples, explanations and resources.
I really get it now thanks to you all. :pray:

Now I can move one step further in my project until I got stuck again :sweat_smile:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.