โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
// The type annotation on collect() is what triggers this behavior
let inputs = ["1", "2", "3", "4"];
let parsed: Result<Vec<i32>, _> = inputs.iter()
.map(|s| s.parse::<i32>()) // Iterator<Item = Result<i32, ParseIntError>>
.collect(); // Result<Vec<i32>, ParseIntError> โ all or nothing
// Ok if everything succeeds
assert_eq!(parsed, Ok(vec![1, 2, 3, 4]));
// Err on first failure โ later items aren't processed
let bad_inputs = ["1", "two", "3"];
let result: Result<Vec<i32>, _> = bad_inputs.iter()
.map(|s| s.parse::<i32>())
.collect();
// => Err(ParseIntError for "two") โ "3" was never attempted
// The turbofish form when the type can't be inferred:
let result = inputs.iter()
.map(|s| s.parse::<i32>())
.collect::<Result<Vec<_>, _>>();
The short-circuit behavior is important: if you have expensive operations after the first bad item, they won't run. If you need to process all items and collect all errors, use `partition` (example 304) instead.
| Concept | OCaml | Rust |
|---|---|---|
| All-or-nothing fold | Manual fold with early return | `.collect::<Result<Vec<_>, _>>()` |
| Short-circuit on error | Manual | Automatic โ first `Err` stops iteration |
| Type annotation | N/A | Required to select the `Result`-collecting `FromIterator` |
| vs. collect all errors | Manual two-pass | Use `partition(Result::is_ok)` instead |
//! 303. Collecting Iterator<Result<T>> into Result<Vec<T>>
//!
//! `collect::<Result<Vec<T>,E>>()` short-circuits on first Err.
fn main() {
// All Ok -> Ok(Vec)
let good: Vec<Result<i32, &str>> = vec![Ok(1), Ok(2), Ok(3)];
let result: Result<Vec<i32>, &str> = good.into_iter().collect();
println!("All ok: {:?}", result);
// One Err -> Err (short-circuits)
let bad: Vec<Result<i32, &str>> = vec![Ok(1), Ok(2), Err("oops"), Ok(4)];
let result: Result<Vec<i32>, &str> = bad.into_iter().collect();
println!("With error: {:?}", result);
// Practical: parse all strings
let inputs = ["1", "2", "3", "4"];
let parsed: Result<Vec<i32>, _> = inputs.iter()
.map(|s| s.parse::<i32>())
.collect();
println!("Parse all: {:?}", parsed);
// Fail case
let bad_inputs = ["1", "two", "3"];
let parsed_bad: Result<Vec<i32>, _> = bad_inputs.iter()
.map(|s| s.parse::<i32>())
.collect();
println!("Parse with bad: {:?}", parsed_bad);
// Collect and process only if all succeed
if let Ok(numbers) = (["10", "20", "30"]).iter()
.map(|s| s.parse::<i32>())
.collect::<Result<Vec<_>, _>>()
{
let sum: i32 = numbers.iter().sum();
println!("Sum of all: {}", sum);
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_collect_all_ok() {
let result: Result<Vec<i32>, &str> = vec![Ok(1), Ok(2), Ok(3)].into_iter().collect();
assert_eq!(result, Ok(vec![1, 2, 3]));
}
#[test]
fn test_collect_with_err() {
let result: Result<Vec<i32>, &str> = vec![Ok(1), Err("bad"), Ok(3)].into_iter().collect();
assert_eq!(result, Err("bad"));
}
#[test]
fn test_collect_parse_ints() {
let ok: Result<Vec<i32>, _> = ["1","2","3"].iter().map(|s| s.parse::<i32>()).collect();
assert_eq!(ok.unwrap(), vec![1, 2, 3]);
}
#[test]
fn test_collect_parse_fails() {
let err: Result<Vec<i32>, _> = ["1","x"].iter().map(|s| s.parse::<i32>()).collect();
assert!(err.is_err());
}
}
(* 303. Collecting Iterator<Result<T>> into Result<Vec<T>> - OCaml *)
let collect_results lst =
let rec aux acc = function
| [] -> Ok (List.rev acc)
| Ok v :: rest -> aux (v :: acc) rest
| Error e :: _ -> Error e
in
aux [] lst
let () =
let good = [Ok 1; Ok 2; Ok 3; Ok 4] in
Printf.printf "All ok: %s\n"
(match collect_results good with
| Ok vs -> "[" ^ String.concat ", " (List.map string_of_int vs) ^ "]"
| Error e -> "Error: " ^ e);
let bad = [Ok 1; Ok 2; Error "oops"; Ok 4] in
Printf.printf "With error: %s\n"
(match collect_results bad with
| Ok _ -> "Ok"
| Error e -> "Error: " ^ e);
let strs = ["1"; "2"; "abc"; "4"] in
let parsed = List.map (fun s ->
match int_of_string_opt s with
| Some n -> Ok n | None -> Error ("not a number: " ^ s)
) strs in
Printf.printf "Parse all: %s\n"
(match collect_results parsed with
| Ok vs -> "[" ^ String.concat ", " (List.map string_of_int vs) ^ "]"
| Error e -> "Error: " ^ e)