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

1010: Partition Results

Difficulty: Intermediate Category: Error Handling Concept: Separating `Ok` and `Err` values from a collection of `Result`s using `partition` Key Insight: When you need ALL results (not just short-circuiting on first error), `partition` + `filter_map` let you process successes and failures independently.
// 1010: Partition Results
// Separate Ok and Err values using Iterator::partition

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

// Approach 1: partition into two Vecs of Results, then unwrap
fn partition_results(inputs: &[&str]) -> (Vec<i64>, Vec<String>) {
    let (oks, errs): (Vec<Result<i64, String>>, Vec<Result<i64, String>>) =
        inputs.iter().map(|s| parse_int(s)).partition(Result::is_ok);

    (
        oks.into_iter().map(Result::unwrap).collect(),
        errs.into_iter().map(Result::unwrap_err).collect(),
    )
}

// Approach 2: Single fold into two accumulators
fn partition_fold(inputs: &[&str]) -> (Vec<i64>, Vec<String>) {
    inputs.iter().map(|s| parse_int(s)).fold(
        (Vec::new(), Vec::new()),
        |(mut oks, mut errs), result| {
            match result {
                Ok(v) => oks.push(v),
                Err(e) => errs.push(e),
            }
            (oks, errs)
        },
    )
}

// Approach 3: Using filter_map for just one side
fn only_successes(inputs: &[&str]) -> Vec<i64> {
    inputs.iter().filter_map(|s| parse_int(s).ok()).collect()
}

fn only_errors(inputs: &[&str]) -> Vec<String> {
    inputs.iter().filter_map(|s| parse_int(s).err()).collect()
}

fn main() {
    let inputs = &["1", "abc", "3", "def", "5"];
    let (oks, errs) = partition_results(inputs);
    println!("Successes: {:?}", oks);
    println!("Failures: {:?}", errs);
    println!("Run `cargo test` to verify all examples.");
}

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

    #[test]
    fn test_partition_mixed() {
        let (oks, errs) = partition_results(&["1", "abc", "3", "def", "5"]);
        assert_eq!(oks, vec![1, 3, 5]);
        assert_eq!(errs, vec!["bad: abc", "bad: def"]);
    }

    #[test]
    fn test_partition_all_ok() {
        let (oks, errs) = partition_results(&["1", "2", "3"]);
        assert_eq!(oks, vec![1, 2, 3]);
        assert!(errs.is_empty());
    }

    #[test]
    fn test_partition_all_err() {
        let (oks, errs) = partition_results(&["a", "b", "c"]);
        assert!(oks.is_empty());
        assert_eq!(errs.len(), 3);
    }

    #[test]
    fn test_fold_matches_partition() {
        let inputs = &["1", "abc", "3"];
        assert_eq!(partition_results(inputs), partition_fold(inputs));
    }

    #[test]
    fn test_filter_map_successes() {
        assert_eq!(only_successes(&["1", "x", "3"]), vec![1, 3]);
    }

    #[test]
    fn test_filter_map_errors() {
        assert_eq!(only_errors(&["1", "x", "3"]), vec!["bad: x"]);
    }

    #[test]
    fn test_empty_input() {
        let (oks, errs) = partition_results(&[]);
        assert!(oks.is_empty());
        assert!(errs.is_empty());
    }
}
(* 1010: Partition Results *)
(* Separate Ok and Err values from a list of results *)

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

(* Approach 1: List.partition with is_ok helper *)
let is_ok = function Ok _ -> true | Error _ -> false

let partition_results results =
  let oks, errs = List.partition is_ok results in
  let unwrap_ok = function Ok v -> v | Error _ -> assert false in
  let unwrap_err = function Error e -> e | Ok _ -> assert false in
  (List.map unwrap_ok oks, List.map unwrap_err errs)

(* Approach 2: Single fold separating into two accumulators *)
let partition_fold results =
  let oks, errs = List.fold_left (fun (oks, errs) r ->
    match r with
    | Ok v -> (v :: oks, errs)
    | Error e -> (oks, e :: errs)
  ) ([], []) results in
  (List.rev oks, List.rev errs)

let test_partition () =
  let inputs = ["1"; "abc"; "3"; "def"; "5"] in
  let results = List.map parse_int inputs in
  let oks, errs = partition_results results in
  assert (oks = [1; 3; 5]);
  assert (errs = ["bad: abc"; "bad: def"]);
  Printf.printf "  Approach 1 (partition + unwrap): passed\n"

let test_fold () =
  let inputs = ["1"; "abc"; "3"; "def"; "5"] in
  let results = List.map parse_int inputs in
  let oks, errs = partition_fold results in
  assert (oks = [1; 3; 5]);
  assert (errs = ["bad: abc"; "bad: def"]);
  (* All ok *)
  let all_ok = List.map parse_int ["1"; "2"; "3"] in
  let oks, errs = partition_fold all_ok in
  assert (oks = [1; 2; 3]);
  assert (errs = []);
  Printf.printf "  Approach 2 (fold): passed\n"

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

๐Ÿ“Š Detailed Comparison

Partition Results โ€” Comparison

Core Insight

`collect()` short-circuits at the first error. When you want ALL successes AND all failures, you need `partition` โ€” it processes every element.

OCaml Approach

  • `List.partition` with a predicate, then unwrap each side
  • Fold-based approach accumulates into two lists
  • Must reverse lists after fold (cons builds in reverse)

Rust Approach

  • `Iterator::partition(Result::is_ok)` splits into two `Vec<Result>`s
  • Then `unwrap`/`unwrap_err` each side (safe because we just partitioned)
  • `filter_map(Result::ok)` / `filter_map(Result::err)` for one-sided extraction
  • Fold approach is also idiomatic

Comparison Table

AspectOCamlRust
Partition`List.partition is_ok``iter.partition(Result::is_ok)`
Unwrap afterManual pattern match`Result::unwrap` (safe post-partition)
One-sided`List.filter_map``filter_map(Result::ok)`
PerformanceTwo passes (partition + map)Same
Use caseCollect all errors for reportingSame