๐Ÿฆ€ Functional Rust

1020: try_fold

Difficulty: Intermediate Category: Error Handling Concept: `try_fold` โ€” a fold operation that short-circuits on the first error Key Insight: `try_fold` is to `fold` what `?` is to unwrap โ€” it gives you accumulation with early exit, perfect for building up a value while validating each step.
// 1020: try_fold โ€” Fold that short-circuits on error

// Approach 1: Iterator::try_fold
fn sum_positive(numbers: &[i64]) -> Result<i64, String> {
    numbers.iter().try_fold(0i64, |acc, &n| {
        if n < 0 {
            Err(format!("negative number: {}", n))
        } else {
            Ok(acc + n)
        }
    })
}

// Approach 2: try_fold with accumulator transformation
fn concat_limited(strings: &[&str], max_len: usize) -> Result<String, String> {
    strings.iter().try_fold(String::new(), |mut acc, &s| {
        acc.push_str(s);
        if acc.len() > max_len {
            Err(format!("result too long: {} > {}", acc.len(), max_len))
        } else {
            Ok(acc)
        }
    })
}

// Approach 3: try_fold vs regular fold comparison
fn product_no_overflow(numbers: &[i64]) -> Result<i64, String> {
    numbers.iter().try_fold(1i64, |acc, &n| {
        acc.checked_mul(n)
            .ok_or_else(|| format!("overflow at {} * {}", acc, n))
    })
}

fn main() {
    println!("sum [1,2,3]: {:?}", sum_positive(&[1, 2, 3]));
    println!("sum [1,-2,3]: {:?}", sum_positive(&[1, -2, 3]));
    println!("concat: {:?}", concat_limited(&["hello", " ", "world"], 20));
    println!("product: {:?}", product_no_overflow(&[2, 3, 4]));
    println!("Run `cargo test` to verify all examples.");
}

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

    #[test]
    fn test_sum_all_positive() {
        assert_eq!(sum_positive(&[1, 2, 3]), Ok(6));
    }

    #[test]
    fn test_sum_negative_fails() {
        let result = sum_positive(&[1, -2, 3]);
        assert_eq!(result, Err("negative number: -2".to_string()));
    }

    #[test]
    fn test_sum_empty() {
        assert_eq!(sum_positive(&[]), Ok(0));
    }

    #[test]
    fn test_concat_ok() {
        assert_eq!(
            concat_limited(&["hello", " ", "world"], 20),
            Ok("hello world".to_string())
        );
    }

    #[test]
    fn test_concat_too_long() {
        let result = concat_limited(&["hello", " ", "world!!!!!!!!!!!!"], 10);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("too long"));
    }

    #[test]
    fn test_product_ok() {
        assert_eq!(product_no_overflow(&[2, 3, 4]), Ok(24));
    }

    #[test]
    fn test_product_overflow() {
        let result = product_no_overflow(&[i64::MAX, 2]);
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("overflow"));
    }

    #[test]
    fn test_short_circuit_proof() {
        // try_fold stops processing after first error
        let mut count = 0;
        let result = [1, -2, 3, 4, 5].iter().try_fold(0, |acc, &n| {
            count += 1;
            if n < 0 { Err("negative") } else { Ok(acc + n) }
        });
        assert!(result.is_err());
        assert_eq!(count, 2); // only processed [1, -2], stopped
    }

    #[test]
    fn test_try_fold_vs_fold() {
        // Regular fold processes everything
        let sum = [1, 2, 3].iter().fold(0, |acc, n| acc + n);
        assert_eq!(sum, 6);

        // try_fold can bail early
        let result = [1, 2, 3].iter().try_fold(0, |acc, &n| {
            if acc + n > 4 { Err("too big") } else { Ok(acc + n) }
        });
        assert!(result.is_err());
    }
}
(* 1020: try_fold โ€” Fold that short-circuits on error *)

(* Approach 1: Manual try_fold *)
let try_fold f init lst =
  let rec aux acc = function
    | [] -> Ok acc
    | x :: rest ->
      match f acc x with
      | Error e -> Error e
      | Ok acc' -> aux acc' rest
  in
  aux init lst

(* Approach 2: Using Seq for lazy evaluation *)
let try_fold_seq f init seq =
  let rec aux acc seq =
    match seq () with
    | Seq.Nil -> Ok acc
    | Seq.Cons (x, rest) ->
      match f acc x with
      | Error e -> Error e
      | Ok acc' -> aux acc' rest
  in
  aux init seq

(* Example: sum numbers, but reject negatives *)
let sum_positive acc n =
  if n < 0 then Error (Printf.sprintf "negative number: %d" n)
  else Ok (acc + n)

(* Example: build string, but limit length *)
let concat_limited acc s =
  let result = acc ^ s in
  if String.length result > 20 then Error "result too long"
  else Ok result

let test_try_fold () =
  assert (try_fold sum_positive 0 [1; 2; 3] = Ok 6);
  (match try_fold sum_positive 0 [1; -2; 3] with
   | Error e -> assert (e = "negative number: -2")
   | Ok _ -> assert false);
  (* Short-circuits: [3] never processed *)
  assert (try_fold sum_positive 0 [] = Ok 0);
  Printf.printf "  Approach 1 (list try_fold): passed\n"

let test_try_fold_seq () =
  let seq = List.to_seq [1; 2; 3] in
  assert (try_fold_seq sum_positive 0 seq = Ok 6);
  let seq = List.to_seq [1; -2; 3] in
  (match try_fold_seq sum_positive 0 seq with
   | Error _ -> ()
   | Ok _ -> assert false);
  Printf.printf "  Approach 2 (seq try_fold): passed\n"

let test_concat () =
  assert (try_fold concat_limited "" ["hello"; " "; "world"] = Ok "hello world");
  (match try_fold concat_limited "" ["hello"; " "; "world!!!!!!!!!!!!!!!!"] with
   | Error e -> assert (e = "result too long")
   | Ok _ -> assert false);
  Printf.printf "  Concat example: passed\n"

let () =
  Printf.printf "Testing try_fold:\n";
  test_try_fold ();
  test_try_fold_seq ();
  test_concat ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

try_fold โ€” Comparison

Core Insight

Regular fold processes all elements. `try_fold` stops at the first failure โ€” essential for validation pipelines and overflow-safe arithmetic.

OCaml Approach

  • No built-in `try_fold` โ€” must write recursive version
  • Pattern match on `Ok`/`Error` at each step
  • Can use `Seq` for lazy evaluation with short-circuit

Rust Approach

  • `Iterator::try_fold` is built-in and optimized
  • Returns `Result<Acc, E>` โ€” Err short-circuits
  • Also works with `Option` (None short-circuits)
  • Compiler can optimize the early-exit path

Comparison Table

AspectOCamlRust
Built-inNoYes (`try_fold`)
Short-circuitManual recursionAutomatic
Works with`Result` (manual)`Result`, `Option`, `ControlFlow`
PerformanceRecursiveOptimized iterator machinery
Related`fold_left``fold`, `try_for_each`