How to return with an error from an iterator chain?

I'm using anyhow for error handling, and itertools to gives some steroid to my iterators :wink:

I am reading a file, line by line, and parsing it. Each line should contains two f64 separated by whitespace. At any point if any kind of parsing fails, I want to return with an error, and add a custom message generated with an error_msg closure (I want to create the message only if there is an error).

TL; DR:

  • How do I convert an Iterator<Item=Result<T>> into a Result<Iterator<Item=T>>?
  • How do I convert an Option<T> into a Result<T, _> to be able to use with_context()? from anyhow?

This works, but it is very verbose and error prone:

        let mut iter = line.split_whitespace();
        let lat: f64 = iter
            .next()
            .with_context(err_msg)?
            .parse()
            .with_context(err_msg)?;
        let lon: f64 = iter
            .next()
            .with_context(err_msg)?
            .parse()
            .with_context(err_msg)?;

        // assert that there is no more items on that line
        assert_eq!(iter.next(), None, "{}", err_msg());

I would like to use the much more concise and less error prone collect_tuple() from itertools instead:

let (lat, lon): (f64, f64) = line
    .split_whitespace()
    .map(|x| x.parse().unwrap())
    .collect_tuple()
    .unwrap();

This works, and it's much cleaner, but I lost my nice error message. How can I add them back?

  • I tried something alike this to be able to to convert the Iterator<Result<T>> into a Result<Iterator<T>> to be able to use the try operator, without success.
iterator
    .map(|x| x.parse())
    .collect::<Result<Iterator<Item=(f64, f64)>>>()?
  • I could use ok_or_else to unwrap the option returned by collect_tuple() (in case there not exactly two numbers in the line), but I don't know how to convert the string returned by err_msg into an Err. I tried to do ok_or_else(|| Err(err_msg())) but I got this error:
110 |             .ok_or_else(|| Err(err_msg()))?;
    |                                           ^ the trait `std::error::Error` is not implemented for `std::result::Result<_, std::string::String>`
    |
    = note: required because of the requirements on the impl of `std::convert::From<std::result::Result<_, std::string::String>>` for `anyhow::Error`
    = note: required by `std::convert::From::from`

AFAIK there's no way to do this without collecting into intermediate storage. The reason is simple - you can't return the Ok, unless the source iterator is already exhausted.

With either ok_or for constant errors or ok_or_else for computable ones. Note that you don't have to wrap the error in Err - it will be wrapped by the function.

1 Like

I can understand that (and effectively, you don't want to consume the whole iterator just to be able to know if any error would be returned when consuming it), but it feels really unfortunate to not be able to stop the iteration at any point if an error is found.

I think I'm missing something obvious:

110 |             .ok_or_else(err_msg)?;
    |                                 ^ the trait `std::error::Error` is not implemented for `std::string::String`
    |
    = note: required because of the requirements on the impl of `std::convert::From<std::string::String>` for `anyhow::Error`
    = note: required by `std::convert::From::from`

EDIT: I didn't knew about the anyhow! macro. This works:

.ok_or_else(||anyhow!(err_msg()))?;

It will be done if you collect into Result<Collection, Error>.

1 Like

I progressed a bit. The following code works, but still feels a bit stupid:

let (lat, lon) = line
    .split_whitespace()
    .map(|x| x.parse().with_context(err_msg))
    .collect::<Result<Vec<_>>>()? // dynamic allocation for no other reason than to bubble the error
    .into_iter() // just before iterating again
    .collect_tuple()
    .ok_or_else(err_msg)?;

This also works, but I'm not sure it's really better

let (lat, lon) = line
    .split_whitespace()
    .map(|x| x.parse().with_context(err_msg))
    .collect_tuple()
    .ok_or_else(err_msg)?;
let (lat, lon) = (lat?, lon?);

You can, and you did. It's just you can't find the possible error without iteration.

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.