๐Ÿฆ€ 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

1009: Collecting Results

Difficulty: Intermediate Category: Error Handling Concept: Transforming `Iterator<Item=Result<T,E>>` into `Result<Vec<T>, E>` using `collect()` Key Insight: Rust's `collect()` has a special `FromIterator` impl for `Result` that short-circuits on the first error โ€” it's the built-in equivalent of OCaml's `sequence` combinator.
// 1009: Collecting Results
// Iterator<Item=Result<T,E>> -> Result<Vec<T>, E> via collect()

fn parse_int(s: &str) -> Result<i64, String> {
    s.parse::<i64>().map_err(|_| format!("bad: {}", s))
}

// Approach 1: collect() โ€” the magic of FromIterator for Result
fn parse_all(inputs: &[&str]) -> Result<Vec<i64>, String> {
    inputs.iter().map(|s| parse_int(s)).collect()
}

// Approach 2: Manual fold for clarity
fn parse_all_manual(inputs: &[&str]) -> Result<Vec<i64>, String> {
    let mut results = Vec::new();
    for s in inputs {
        results.push(parse_int(s)?);
    }
    Ok(results)
}

// Approach 3: Using try_fold
fn parse_all_fold(inputs: &[&str]) -> Result<Vec<i64>, String> {
    inputs.iter().try_fold(Vec::new(), |mut acc, s| {
        acc.push(parse_int(s)?);
        Ok(acc)
    })
}

fn main() {
    let good = &["1", "2", "3"];
    let bad = &["1", "abc", "3"];

    println!("Good: {:?}", parse_all(good));
    println!("Bad: {:?}", parse_all(bad));
    println!("Run `cargo test` to verify all examples.");
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_collect_all_ok() {
        assert_eq!(parse_all(&["1", "2", "3"]), Ok(vec![1, 2, 3]));
    }

    #[test]
    fn test_collect_first_error() {
        let result = parse_all(&["1", "abc", "3"]);
        assert_eq!(result, Err("bad: abc".to_string()));
    }

    #[test]
    fn test_collect_empty() {
        assert_eq!(parse_all(&[]), Ok(vec![]));
    }

    #[test]
    fn test_manual_matches_collect() {
        let inputs = &["10", "20", "30"];
        assert_eq!(parse_all(inputs), parse_all_manual(inputs));

        let bad = &["10", "x"];
        assert!(parse_all_manual(bad).is_err());
    }

    #[test]
    fn test_fold_matches_collect() {
        let inputs = &["5", "10", "15"];
        assert_eq!(parse_all(inputs), parse_all_fold(inputs));
    }

    #[test]
    fn test_short_circuit_behavior() {
        // collect() on Result short-circuits at first Err
        let mut count = 0;
        let result: Result<Vec<i64>, String> = ["1", "bad", "3"]
            .iter()
            .map(|s| {
                count += 1;
                parse_int(s)
            })
            .collect();
        assert!(result.is_err());
        // Iterator is lazy โ€” may stop at error
        assert!(count <= 3);
    }

    #[test]
    fn test_single_element() {
        assert_eq!(parse_all(&["42"]), Ok(vec![42]));
        assert!(parse_all(&["xyz"]).is_err());
    }
}
(* 1009: Collecting Results *)
(* Turning a list of results into a result of list *)

let parse_int s =
  match int_of_string_opt s with
  | Some n -> Ok n
  | None -> Error (Printf.sprintf "bad: %s" s)

(* Approach 1: Manual fold โ€” short-circuits on first error *)
let collect_results results =
  List.fold_left (fun acc r ->
    match acc, r with
    | Error e, _ -> Error e        (* already failed *)
    | _, Error e -> Error e        (* new failure *)
    | Ok xs, Ok x -> Ok (xs @ [x]) (* both ok, accumulate *)
  ) (Ok []) results

(* Approach 2: Tail-recursive with early exit *)
let rec sequence_results acc = function
  | [] -> Ok (List.rev acc)
  | Ok x :: rest -> sequence_results (x :: acc) rest
  | Error e :: _ -> Error e

let sequence rs = sequence_results [] rs

(* Approach 3: map then sequence *)
let traverse f xs =
  List.map f xs |> sequence

let test_fold () =
  let inputs = ["1"; "2"; "3"] in
  let results = List.map parse_int inputs in
  assert (collect_results results = Ok [1; 2; 3]);
  let bad_inputs = ["1"; "abc"; "3"] in
  let bad_results = List.map parse_int bad_inputs in
  (match collect_results bad_results with
   | Error e -> assert (e = "bad: abc")
   | Ok _ -> assert false);
  Printf.printf "  Approach 1 (fold): passed\n"

let test_sequence () =
  assert (sequence [Ok 1; Ok 2; Ok 3] = Ok [1; 2; 3]);
  (match sequence [Ok 1; Error "fail"; Ok 3] with
   | Error "fail" -> ()
   | _ -> assert false);
  Printf.printf "  Approach 2 (sequence): passed\n"

let test_traverse () =
  assert (traverse parse_int ["10"; "20"; "30"] = Ok [10; 20; 30]);
  assert (traverse parse_int [] = Ok []);
  (match traverse parse_int ["1"; "x"] with
   | Error _ -> ()
   | Ok _ -> assert false);
  Printf.printf "  Approach 3 (traverse): passed\n"

let () =
  Printf.printf "Testing collecting results:\n";
  test_fold ();
  test_sequence ();
  test_traverse ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Collecting Results โ€” Comparison

Core Insight

Converting `[Result<T,E>]` to `Result<[T], E>` is a fundamental operation in both languages. Rust builds it into `collect()` via the type system; OCaml requires a manual combinator.

OCaml Approach

  • Write `sequence` or `traverse` manually (fold + reverse)
  • No stdlib function for `Result list -> list Result` before external libs
  • Must be explicit about short-circuit behavior
  • Libraries like `Base` or `Lwt` provide this

Rust Approach

  • `iter.collect::<Result<Vec<T>, E>>()` โ€” one line, built-in
  • `FromIterator` trait impl handles the short-circuit
  • `try_fold` for more control over accumulation
  • Type inference usually figures out the target type

Comparison Table

AspectOCamlRust
Built-inNo (manual `sequence`)Yes (`collect()`)
Short-circuitsMust implementAutomatic
Type inferenceN/ADrives `collect()` target
Traverse (map+collect)Manual `traverse``.map(f).collect()`
Empty input`Ok []``Ok(vec![])`