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

304: Splitting Ok/Err with partition()

Difficulty: 2 Level: Intermediate Process all items in a batch and separate the successes from the failures.

The Problem This Solves

You're processing a batch of user records โ€” parsing ages, validating emails, reading config values. Some will fail. With `collect::<Result<Vec>>()`, the first failure stops everything: you get one error and lose all the successes. For batch processing, that's wrong โ€” you want to know all the failures, not just the first one. Real production batch jobs need a different contract: process everything, collect the successes, collect the failures, report both. An import script that stops at the first bad row and loses the other 999 valid ones is broken. A script that processes all rows and reports "imported 847, failed 153 (see attached log)" is useful. `partition(Result::is_ok)` splits a `Vec<Result<T, E>>` into two `Vec`s in a single pass โ€” all `Ok` items in one, all `Err` items in the other. No short-circuiting. Everything is processed.

The Intuition

`partition(Result::is_ok)` is the "process all, separate outcomes" alternative to `collect::<Result<Vec>>()` โ€” it never stops early, and you get both successes and failures.

How It Works in Rust

let inputs = ["1", "two", "3", "four", "5"];

// Step 1: map to Result
let results: Vec<Result<i32, &str>> = inputs.iter()
 .map(|s| s.parse::<i32>().map_err(|_| *s))
 .collect();

// Step 2: partition into Ok and Err groups (single pass, processes ALL)
let (successes, failures): (Vec<_>, Vec<_>) = results
 .into_iter()
 .partition(Result::is_ok);

// Step 3: unwrap each group (safe โ€” partition guarantees type)
let nums: Vec<i32> = successes.into_iter().flatten().collect();
//  โ†‘ .flatten() on Result<T, E> yields the Ok value
let bad: Vec<&str> = failures.into_iter().map(|r| r.unwrap_err()).collect();

println!("Parsed: {:?}", nums);       // [1, 3, 5]
println!("Unparseable: {:?}", bad);   // ["two", "four"]

// Practical: batch report
println!("{} succeeded, {} failed", nums.len(), bad.len());
The `.flatten()` trick works because `IntoIterator for Result<T, E>` yields zero items for `Err` and one item for `Ok`. After partition, you know all items in `successes` are `Ok`, so `flatten()` safely extracts the values.

What This Unlocks

Key Differences

ConceptOCamlRust
Split into two listsManual fold into pair`partition(Result::is_ok)`
Short-circuits?No (manual)No โ€” all items processed
vs. collect::\<Result\>Stops at first ErrNever stops โ€” collects all
Extract values afterManual pattern match`.flatten()` on Ok group; `.map(unwrap_err)` on Err group
//! # Splitting Ok/Err with partition()
//!
//! `partition(Result::is_ok)` collects ALL successes and ALL failures in one pass.

/// Partition results into successes and failures
pub fn partition_results<T: std::fmt::Debug, E: std::fmt::Debug>(results: Vec<Result<T, E>>) -> (Vec<T>, Vec<E>) {
    let (oks, errs): (Vec<_>, Vec<_>) = results.into_iter().partition(Result::is_ok);
    let ok_vals: Vec<T> = oks.into_iter().map(|r| r.unwrap()).collect();
    let err_vals: Vec<E> = errs.into_iter().map(|r| r.unwrap_err()).collect();
    (ok_vals, err_vals)
}

/// Parse all strings, collecting both successes and failures
pub fn parse_all_report(inputs: &[&str]) -> (Vec<i32>, Vec<String>) {
    let results: Vec<Result<i32, String>> = inputs
        .iter()
        .map(|s| s.parse::<i32>().map_err(|_| s.to_string()))
        .collect();
    partition_results(results)
}

/// Alternative using fold for more control
pub fn partition_fold<T, E>(results: Vec<Result<T, E>>) -> (Vec<T>, Vec<E>) {
    results.into_iter().fold((vec![], vec![]), |(mut oks, mut errs), r| {
        match r {
            Ok(v) => oks.push(v),
            Err(e) => errs.push(e),
        }
        (oks, errs)
    })
}

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

    #[test]
    fn test_partition_results() {
        let v: Vec<Result<i32, &str>> = vec![Ok(1), Err("bad"), Ok(3)];
        let (oks, errs) = partition_results(v);
        assert_eq!(oks, vec![1, 3]);
        assert_eq!(errs, vec!["bad"]);
    }

    #[test]
    fn test_partition_all_ok() {
        let v: Vec<Result<i32, &str>> = vec![Ok(1), Ok(2)];
        let (oks, errs) = partition_results(v);
        assert_eq!(oks, vec![1, 2]);
        assert!(errs.is_empty());
    }

    #[test]
    fn test_parse_all_report() {
        let (nums, bad) = parse_all_report(&["1", "two", "3", "four"]);
        assert_eq!(nums, vec![1, 3]);
        assert_eq!(bad, vec!["two", "four"]);
    }

    #[test]
    fn test_partition_fold() {
        let v: Vec<Result<i32, &str>> = vec![Ok(1), Err("x"), Ok(2)];
        let (oks, errs) = partition_fold(v);
        assert_eq!(oks, vec![1, 2]);
        assert_eq!(errs, vec!["x"]);
    }

    #[test]
    fn test_empty_input() {
        let v: Vec<Result<i32, &str>> = vec![];
        let (oks, errs) = partition_results(v);
        assert!(oks.is_empty());
        assert!(errs.is_empty());
    }
}
(* 304. Splitting Ok/Err with partition() - OCaml *)

let partition_results lst =
  List.fold_right (fun r (oks, errs) ->
    match r with
    | Ok v -> (v :: oks, errs)
    | Error e -> (oks, e :: errs)
  ) lst ([], [])

let () =
  let results = [Ok 1; Error "bad1"; Ok 3; Error "bad2"; Ok 5] in
  let (oks, errs) = partition_results results in
  Printf.printf "Ok values: %s\n"
    (String.concat ", " (List.map string_of_int oks));
  Printf.printf "Errors: %s\n"
    (String.concat ", " errs);

  let inputs = ["1"; "two"; "3"; "four"; "5"] in
  let parsed = List.map (fun s ->
    match int_of_string_opt s with
    | Some n -> Ok n | None -> Error s
  ) inputs in
  let (nums, bad_strs) = partition_results parsed in
  Printf.printf "Parsed: %s\nFailed: %s\n"
    (String.concat ", " (List.map string_of_int nums))
    (String.concat ", " bad_strs)