2022-10-19
I learnt about Rust's collect method, and think it's pretty cool. It transforms an interator into a collection, and allows some interesting transformations on the shape of data.
What type of collection the iterator is transformed into can be determined by the type annotation alone:
let array = [1, 2, 3];
let iterator = array.into_iter();
let vector: Vec<i32>= iterator.collect();
collect with ResultCollect can be used to transform an iterator containing Results into a single Result.
If all the Results contained in the interator are success values, collect will return a success value containing an iterator of those values:
let results: Vec<Result<i32, &str>> = vec![Ok(1), Ok(3)];
let iterator = results.into_iter();
let collected: Result<Vec<i32>, &str> = iterator.collect();
println!("{:?}", collected);
Ok([1, 3])
If there is an error value in the iterator, an error will be returned. If there are multiple errors, the first one is returned.
let results: Vec<Result<i32, &str>> = vec![Ok(1), Err("nope"), Err("oops"), Ok(3)];
let iterator = results.into_iter();
let collected: Result<Vec<i32>, &str> = iterator.collect();
println!("{:?}", collected);
Err("nope")
I can see this being very useful: a lot of the time when one thing fails, we treat the whole thing as being a failure. Though, in cases where computing each result is costly we may want to fail early rather than compute them all and then collect before failing.
What I like about Rust's collect is that it allows us to transform an object by changing its container. Instead of data in an iterator, we can have the same data in a vector.
With a Result error case, the data changes.
With a Result success case, the data stays the same and the container changes. While the data was each wrapped in a Result, and then altogether in an iterator, it becomes wrapped altogether in a vector (or whatever container is specified), and then wrapped again in a Result. In a way, the data is lifted from one context to another.