๐Ÿฆ€ Functional Rust
๐ŸŽฌ Error Handling in Rust Option, Result, the ? operator, and combinators.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Option represents a value that may or may not exist โ€” Some(value) or None

โ€ข Result represents success (Ok) or failure (Err) โ€” no exceptions needed

โ€ข 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

065: Traverse with Result

Difficulty: โญโญ Level: Foundations Apply a fallible function to every element in a list and collect all successes โ€” or stop at the first failure.

The Problem This Solves

You have a list of raw inputs โ€” strings from a form, rows from a CSV, IDs from a request โ€” and you want to parse or validate each one. If any input is invalid, you want the whole thing to fail with the first error. If all are valid, you want the collected results. In JavaScript this is `Promise.all()`: give it an array of Promises, get back a Promise of an array. If any Promise rejects, the whole thing rejects. `traverse` is the synchronous, pure version of that pattern. Without it, you'd write a loop, accumulate results, check for errors, propagate manually. It gets ugly fast and every version looks slightly different. Rust makes this a one-liner using the compiler's understanding of `Result`.

The Intuition

The magic is in `.collect::<Result<Vec<_>, _>>()`. Rust's `collect()` is polymorphic โ€” it knows how to collect an iterator of `Result<T, E>` values into a `Result<Vec<T>, E>`. It runs through the iterator, accumulates successes, and short-circuits on the first `Err`. You get exactly the `Promise.all()` semantics for free. In OCaml, you write this yourself using `fold_right` or explicit recursion. In Rust, you just ask `collect()` to produce the right type and the compiler handles it.

How It Works in Rust

// 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
}

What This Unlocks

Key Differences

ConceptOCamlRust
ImplementationRecursive or `fold_right``.collect::<Result<Vec<_>,_>>()`
Error short-circuitPattern match on `Error e`Built into `collect()`
`sequence``traverse Fun.id`Same `.collect()` on `Vec<Result<T,E>>`
Explicit foldManual `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"

๐Ÿ“Š Detailed Comparison

Comparison: Traverse with Result

Traverse

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!
}

Sequence

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()
}