Convert iterator over Result into Result<Vec<_>, _>

Viewed 736

I am trying to write a function that converts the output from glob which is Result<Paths, PatternError> into Result<Vec<PathBuf>, Error>. I have my own Error type that implements From for both glob::GlobError and glob::PatternError. Currently I have the following implemented:

fn glob_abs_path(fpath: &str) -> Result<Vec<PathBuf>, Error> {
    Ok(glob(fpath)?.map(|val| val.unwrap()).collect::<Vec<_>>())
}

What I am looking for is a way to remove the call to val.unwrap and allow for a GlobError to be returned if encountered. I tried using collect::<Result<Vec<_>>>()? but that did not work.

Apologies if the question title is cryptic. I am new to rust and don't have the familiarity yet to express this question concisely.

1 Answers

There are a couple of problems here:

fn glob_abs_path(fpath: &str) -> std::result::Result<Vec<PathBuf>, Error> {
  Ok(glob(fpath)?.map(|val| val.unwrap()).collect::<Vec<_>>())
}

At the end of glob_abs_path, you are collecting the value into a Vec. If you try to use the ? operator on a vec, it will fail:

error[E0277]: the `?` operator can only be applied to values that implement `std::ops::Try`
  --> src/lib.rs:26:8
   |
26 |     Ok(glob(fpath)?.map(|val| val.unwrap()).collect::<Vec<_>>()?)
   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the `?` operator cannot be applied 
to type `std::vec::Vec<std::result::Result<std::path::PathBuf, glob::GlobError>>`

The ? operator can only be applied to values that implement std::ops::Try, namely Result<_, _>. So what we have to do is collect the iterator into a Result:

fn glob_abs_path(fpath: &str) -> std::result::Result<Vec<PathBuf>, Error> {
  Ok(glob(fpath)?.map(|val| val.unwrap()).collect::<std::result::Result<Vec<_>, _>>()?)
}

However, we get another error here:

error[E0277]: a value of type `std::result::Result<std::vec::Vec<_>, _>` cannot be built 
from an iterator over elements of type `std::path::PathBuf`
  --> src/lib.rs:26:43
   |
26 |   Ok(glob(fpath)?.map(|val| val.unwrap()).collect::<std::result::Result<Vec<_>, _>>())
   |                                           ^^^^^^^ value of type 
   |  `std::result::Result<std::vec::Vec<_>, _>` cannot be built from 
   |  `std::iter::Iterator<Item=std::path::PathBuf>`
   |

The issue here is that we are unwrapping the value .map(|val| val.unwrap()) before collecting it, and the iterator does not know how to convert the unwrapped value (PathBuf ) to a Result. Removing the call to unwrap fixes the issue:

fn glob_abs_path(fpath: &str) -> std::result::Result<Vec<PathBuf>, Error> {
    Ok(glob(fpath)?.collect::<std::result::Result<Vec<_>, _>>()?)
}

The code now compiles. You mentioned that you had a type alias for Result:

type MyResult<T> = std::result::Result<T, Error>

You can use this type alias in the return value:

fn glob_abs_path(fpath: &str) -> MyResult<Vec<PathBuf>> {
    Ok(glob(fpath)?.collect::<std::result::Result<Vec<_>, _>>()?)
}

However, when you use it in to collect the iterator:

fn glob_abs_path(fpath: &str) -> MyResult<Vec<PathBuf>> {
    Ok(glob(fpath)?.collect::<MyResult<Vec<_>>>()?)
}

It fails:

error[E0277]: a value of type `std::result::Result<std::vec::Vec<_>, Error>` 
cannot be built from an iterator over elements of type `std::result::Result<std::path::PathBuf, glob::GlobError>`
  --> src/lib.rs:26:21
   |
26 |     Ok(glob(fpath)?.collect::<MyResult<Vec<_>>>()?)
   |                     ^^^^^^^ value of type `std::result::Result<std::vec::Vec<_>, Error>` 
   |  cannot be built from `std::iter::Iterator<Item=std::result::Result<std::path::PathBuf, glob::GlobError>>`
   |

This is because MyResult explicitly states the return value as your custom Error type. Iterator cannot convert GlobError to your Error. That conversion is handled by the ? operator:

fn glob_abs_path(fpath: &str) -> MyResult<Vec<PathBuf>> {
  Ok(glob(fpath)?.collect::<std::result::Result<Vec<_>, GlobError>>()?)
}

As you can see above, the iterator is collected into <std::result::Result<Vec<_>, GlobError>, and then converted into std::result::Result<Vec<_>, YourError> (MyResult) by the ? operator.

Here is the final working code as a Rust Playground

Related