How to find the index of an element in a Vec of Results, stopping if an Err is found?

86 Views Asked by At

Suppose I have a collection of Result items, like:

let items: &[Result<&str, u32>] = &[Ok("foo"), Err(444), Ok("bar")];

I need to find the index of the first element with the value "bar". My first attempt looks like:

let bar_idx = items.iter()
    .position(|item| item? == "bar")?;

This won't work because of the ? operator inside the closure.

I'm aware of the try_for_each method, which fails the entire collection if an Err is returned, but there is no such equivalent for position, which is what I'm trying to do.

How can I implement this search?

EDIT:

I opened an issue at Rust repo, because I believe try_position should exist.

2

There are 2 best solutions below

1
Chayim Friedman On BEST ANSWER

You can build one on top of try_for_each():

use std::ops::ControlFlow;
let item: ControlFlow<Result<usize, u32>> =
    items
        .iter()
        .enumerate()
        .try_for_each(|(idx, item)| match item {
            Ok(v) if *v == "bar" => ControlFlow::Break(Ok(idx)),
            Ok(_) => ControlFlow::Continue(()),
            Err(err) => ControlFlow::Break(Err(*err)),
        });
// Can replace by `ControlFlow::break_value()` once stabilized.
let item = match item {
    ControlFlow::Break(v) => Some(v),
    ControlFlow::Continue(()) => None,
};

But frankly, it's likely better to use a simple for loop:

let mut result = None;
for (idx, item) in items.iter().enumerate() {
    match item {
        Ok(v) if *v == "bar" => {
            result = Some(Ok(idx));
            break;
        }
        Ok(_) => {}
        Err(err) => {
            result = Some(Err(*err));
            break;
        }
    }
}
2
ohmycloudy On
fn main() {
    let items: &[Result<&str, u32>] = &[Ok("foo"), Err(404), Ok("bar")];

    // find the index of the first element with the value "bar"
    let idx1 = items.iter().position(|x| x.is_ok_and(|x| x == "bar"));
    println!("{:?}", idx1);

    // or you can combine `enumerate`,`find` and `map` to find the index
    let idx2 = items.iter()
        .enumerate()
        .find(|(index, value)| {
            if **value == Ok("bar") {
                true
            } else {
                false
            }
        })
        .map(|x| x.0);
    println!("{:?}", idx2);
}

Both print Some(2) here.If nothing found, print None.