Search code examples
rusterror-handlingiterator

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


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.


Solution

  • 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;
            }
        }
    }