โข 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
// Apply a fallible function to each element, collect or fail
fn traverse_result<T, U, E, F: Fn(&T) -> Result<U, E>>(
xs: &[T],
f: F,
) -> Result<Vec<U>, E> {
xs.iter().map(f).collect() // that's it โ collect knows what to do
}
The type annotation on `collect()` is the key โ you're asking for `Result<Vec<_>, _>`:
// Explicit type annotation form:
let results: Result<Vec<i32>, String> = inputs
.iter()
.map(|s| s.parse::<i32>().map_err(|e| e.to_string()))
.collect::<Result<Vec<_>, _>>();
Sequence (traverse with identity โ "flip" a `Vec<Result>` into a `Result<Vec>`):
fn sequence_result<T, E>(xs: Vec<Result<T, E>>) -> Result<Vec<T>, E> {
xs.into_iter().collect() // same trick
}
| Concept | OCaml | Rust |
|---|---|---|
| Implementation | Recursive or `fold_right` | `.collect::<Result<Vec<_>,_>>()` |
| Error short-circuit | Pattern match on `Error e` | Built into `collect()` |
| `sequence` | `traverse Fun.id` | Same `.collect()` on `Vec<Result<T,E>>` |
| Explicit fold | Manual `fold_right` | `try_fold` on iterator |
// Example 065: Traverse with Result
// Turn Vec<Result<T,E>> into Result<Vec<T>,E>
// Approach 1: Using collect (Rust's built-in traverse for Result!)
fn traverse_result<T, U, E, F: Fn(&T) -> Result<U, E>>(xs: &[T], f: F) -> Result<Vec<U>, E> {
xs.iter().map(f).collect()
}
// Approach 2: Using try_fold
fn traverse_result_fold<T, U, E, F: Fn(&T) -> Result<U, E>>(xs: &[T], f: F) -> Result<Vec<U>, E> {
xs.iter().try_fold(Vec::new(), |mut acc, x| {
acc.push(f(x)?);
Ok(acc)
})
}
// Approach 3: Sequence
fn sequence_result<T, E>(xs: Vec<Result<T, E>>) -> Result<Vec<T>, E> {
xs.into_iter().collect()
}
fn parse_positive(s: &&str) -> Result<i32, String> {
let n: i32 = s.parse().map_err(|_| format!("Not a number: {}", s))?;
if n <= 0 {
Err(format!("Not positive: {}", n))
} else {
Ok(n)
}
}
fn validate_username(s: &&str) -> Result<String, String> {
if s.len() < 3 { Err("Too short".into()) }
else if s.len() > 20 { Err("Too long".into()) }
else { Ok(s.to_string()) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_traverse_all_ok() {
assert_eq!(traverse_result(&["1", "2", "3"], parse_positive), Ok(vec![1, 2, 3]));
}
#[test]
fn test_traverse_parse_error() {
assert_eq!(traverse_result(&["1", "bad", "3"], parse_positive), Err("Not a number: bad".into()));
}
#[test]
fn test_traverse_validation_error() {
assert_eq!(traverse_result(&["1", "-2", "3"], parse_positive), Err("Not positive: -2".into()));
}
#[test]
fn test_traverse_empty() {
let empty: &[&str] = &[];
assert_eq!(traverse_result(empty, parse_positive), Ok(vec![]));
}
#[test]
fn test_fold_version() {
assert_eq!(traverse_result_fold(&["1", "2"], parse_positive), Ok(vec![1, 2]));
assert_eq!(traverse_result_fold(&["1", "bad"], parse_positive), Err("Not a number: bad".into()));
}
#[test]
fn test_sequence_ok() {
assert_eq!(sequence_result::<i32, String>(vec![Ok(1), Ok(2), Ok(3)]), Ok(vec![1, 2, 3]));
}
#[test]
fn test_sequence_err() {
let rs: Vec<Result<i32, &str>> = vec![Ok(1), Err("e"), Ok(3)];
assert_eq!(sequence_result(rs), Err("e"));
}
#[test]
fn test_validate_usernames() {
assert_eq!(traverse_result(&["alice", "bob"], validate_username), Ok(vec!["alice".into(), "bob".into()]));
assert_eq!(traverse_result(&["alice", "ab"], validate_username), Err("Too short".into()));
}
}
(* Example 065: Traverse with Result *)
(* Turn a list of Results into a Result of list *)
(* Approach 1: Recursive traverse *)
let rec traverse_result f = function
| [] -> Ok []
| x :: xs ->
match f x with
| Error e -> Error e
| Ok y ->
match traverse_result f xs with
| Error e -> Error e
| Ok ys -> Ok (y :: ys)
(* Approach 2: fold_right based *)
let traverse_result_fold f xs =
List.fold_right (fun x acc ->
match f x, acc with
| Ok y, Ok ys -> Ok (y :: ys)
| Error e, _ | _, Error e -> Error e
) xs (Ok [])
(* Approach 3: sequence *)
let sequence_result xs = traverse_result Fun.id xs
(* Test functions *)
let parse_positive s =
match int_of_string_opt s with
| None -> Error (Printf.sprintf "Not a number: %s" s)
| Some n when n <= 0 -> Error (Printf.sprintf "Not positive: %d" n)
| Some n -> Ok n
let validate_username s =
if String.length s < 3 then Error "Too short"
else if String.length s > 20 then Error "Too long"
else Ok s
let () =
assert (traverse_result parse_positive ["1"; "2"; "3"] = Ok [1; 2; 3]);
assert (traverse_result parse_positive ["1"; "bad"; "3"] = Error "Not a number: bad");
assert (traverse_result parse_positive ["1"; "-2"; "3"] = Error "Not positive: -2");
assert (traverse_result parse_positive [] = Ok []);
assert (traverse_result_fold parse_positive ["1"; "2"; "3"] = Ok [1; 2; 3]);
assert (sequence_result [Ok 1; Ok 2; Ok 3] = Ok [1; 2; 3]);
assert (sequence_result [Ok 1; Error "e"; Ok 3] = Error "e");
assert (traverse_result validate_username ["alice"; "bob"] = Ok ["alice"; "bob"]);
assert (traverse_result validate_username ["alice"; "ab"] = Error "Too short");
Printf.printf "โ All tests passed\n"
OCaml:
๐ช Show OCaml equivalent
let rec traverse_result f = function
| [] -> Ok []
| x :: xs ->
match f x with
| Error e -> Error e
| Ok y -> match traverse_result f xs with
| Error e -> Error e
| Ok ys -> Ok (y :: ys)
Rust:
fn traverse_result<T, U, E, F: Fn(&T) -> Result<U, E>>(xs: &[T], f: F) -> Result<Vec<U>, E> {
xs.iter().map(f).collect() // Built-in!
}OCaml:
๐ช Show OCaml equivalent
let sequence_result xs = traverse_result Fun.id xs
Rust:
fn sequence_result<T, E>(xs: Vec<Result<T, E>>) -> Result<Vec<T>, E> {
xs.into_iter().collect()
}