Iterator::collect can collect Results
Iterator::collect is often used to iterate values in an iterator and store them into a collection. For example, print!("{:?}", (1..10).map(|n| n.to_string()).collect::<Vec<_>>()) will print ["1", "2", "3", "4", "5", "6", "7", "8", "9"].
You can also use Iterator::collect to Iterator<Result<T, E>> to Result<V<T>, E> where V<T> is a collection of T.
For example,
print!(
"{:?}",
(1..10)
.map(|n| Ok(n))
.collect::<Result<Vec<_>, std::io::Error>>()
);
prints Ok([1, 2, 3, 4, 5, 6, 7, 8, 9]).
When an iterator contains an error, it’ll be collected to the first error.
print!(
"{:?}",
[
Ok(1),
Ok(2),
Err(std::io::Error::other("error1")),
Ok(4),
Err(std::io::Error::other("error2"))
]
.into_iter()
.collect::<Result<Vec<_>, std::io::Error>>()
);
prints Err(Custom { kind: Other, error: "error1" }).
This works with Option as well.
print!(
"{:?}",
["1", "2", "3", "4"]
.iter()
.map(|n| n.parse::<i32>().ok())
.collect::<Option<Vec<_>>>()
);
prints Some([1, 2, 3, 4]), while
print!(
"{:?}",
["1", "2", "x", "4"]
.iter()
.map(|n| n.parse::<i32>().ok())
.collect::<Option<Vec<_>>>()
);
prints None.
You can think it as sequenceA with Either e (or Maybe) in Haskell. Like sequenceA (map Right [1..9]) becomes Right [1,2,3,4,5,6,7,8,9], and sequenceA (map (readMaybe @Int) ["1", "2", "x", "4"]) becomes Nothing.
The following snippet reads files specified by command-line arguments, splits content of each file into lines, then concatenates all lines (and prints them).
use std::env;
use std::fs;
use std::io;
fn main() -> io::Result<()> {
let lines = env::args()
.skip(1)
.map(|path| {
Ok(
fs::read_to_string(&path)?
.lines()
.map(String::from)
.collect::<Vec<_>>(), // Iterator<String> to Vec<String>
)
})
.collect::<io::Result<Vec<_>>>()? // Iterator<io::Result<Vec<String>>> to io::Result<Vec<Vec<String>>>
.into_iter()
.flatten()
.collect::<Vec<_>>(); // Iterator<String> to Vec<String>
print!("{:?}", lines);
Ok(())
}
As you can see, we use three collects. Two to convert Iterator<String> to Vec<String>, and one to convert Iterator<io::Result<Vec<String>>> to io::Result<Vec<Vec<String>>>.
You can write similar in Haskell like this.
import Control.Exception
import System.Environment
main :: IO ()
main = do
args <- getArgs
Right lines <-
(fmap concat . sequenceA)
<$> traverse
( \path ->
fmap lines
<$> try @IOError (readFile path)
)
args
print lines