🦀 Functional Rust
🎬 How Rust Iterators Work Lazy evaluation, chaining, collect(), and zero-cost abstractions.
📝 Text version (for readers / accessibility)

• Iterators are lazy — .map(), .filter(), .take() build a chain but do no work until consumed

• .collect() triggers evaluation, transforming the chain into a Vec, HashMap, or other collection

• Zero-cost abstraction: iterator chains compile to the same machine code as hand-written loops

• .iter() borrows, .into_iter() consumes, .iter_mut() borrows mutably

• Chaining replaces nested loops with a readable, composable pipeline

084: Phone Number Parser — Validation Pipeline

Difficulty: Intermediate Category: Error Handling Concept: Chaining validations with Result/and_then Key Insight: OCaml's `Result.bind` and Rust's `.and_then()` are the same monadic bind operation, enabling clean validation pipelines.
/// Phone Number Parser — Validation Pipeline
///
/// Ownership: Input is borrowed &str. Result returns owned String on success.
/// The and_then chain mirrors OCaml's Result.bind pipeline.

/// Extract only digits from input
fn digits_only(s: &str) -> String {
    s.chars().filter(|c| c.is_ascii_digit()).collect()
}

/// Validate a phone number using Result chaining
pub fn validate(s: &str) -> Result<String, &'static str> {
    let d = digits_only(s);

    // Normalize 11-digit numbers starting with 1
    let d = if d.len() == 11 && d.starts_with('1') {
        d[1..].to_string()
    } else if d.len() == 10 {
        d
    } else {
        return Err("wrong number of digits");
    };

    // Validate area code
    let area = d.as_bytes()[0];
    if area == b'0' || area == b'1' {
        return Err("invalid area code");
    }

    // Validate exchange
    let exchange = d.as_bytes()[3];
    if exchange == b'0' || exchange == b'1' {
        return Err("invalid exchange");
    }

    Ok(d)
}

/// Version 2: Using and_then chain (more functional)
pub fn validate_chain(s: &str) -> Result<String, &'static str> {
    let d = digits_only(s);

    normalize_length(d)
        .and_then(check_area_code)
        .and_then(check_exchange)
}

fn normalize_length(d: String) -> Result<String, &'static str> {
    match d.len() {
        11 if d.starts_with('1') => Ok(d[1..].to_string()),
        10 => Ok(d),
        _ => Err("wrong number of digits"),
    }
}

fn check_area_code(d: String) -> Result<String, &'static str> {
    if d.as_bytes()[0] == b'0' || d.as_bytes()[0] == b'1' {
        Err("invalid area code")
    } else {
        Ok(d)
    }
}

fn check_exchange(d: String) -> Result<String, &'static str> {
    if d.as_bytes()[3] == b'0' || d.as_bytes()[3] == b'1' {
        Err("invalid exchange")
    } else {
        Ok(d)
    }
}

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

    #[test]
    fn test_valid_10_digit() {
        assert_eq!(validate("(223) 456-7890"), Ok("2234567890".into()));
    }

    #[test]
    fn test_valid_11_digit() {
        assert_eq!(validate("1-223-456-7890"), Ok("2234567890".into()));
    }

    #[test]
    fn test_invalid_area_code() {
        assert_eq!(validate("(023) 456-7890"), Err("invalid area code"));
    }

    #[test]
    fn test_invalid_exchange() {
        assert_eq!(validate("(223) 056-7890"), Err("invalid exchange"));
    }

    #[test]
    fn test_wrong_length() {
        assert_eq!(validate("123"), Err("wrong number of digits"));
    }

    #[test]
    fn test_chain_version() {
        assert_eq!(validate_chain("(223) 456-7890"), Ok("2234567890".into()));
        assert_eq!(validate_chain("(023) 456-7890"), Err("invalid area code"));
    }
}

fn main() {
    println!("{:?}", validate("(223) 456-7890"), Ok("2234567890".into()));
    println!("{:?}", validate("1-223-456-7890"), Ok("2234567890".into()));
    println!("{:?}", validate("(023) 456-7890"), Err("invalid area code"));
}
(* Phone Number Parser — Validation Pipeline *)

let digits_only s =
  String.to_seq s |> Seq.filter (fun c -> c >= '0' && c <= '9')
  |> String.of_seq

(* Version 1: Result.bind chain *)
let validate s =
  let d = digits_only s in
  let n = String.length d in
  if n = 11 && d.[0] = '1' then Ok (String.sub d 1 10)
  else if n = 10 then Ok d
  else Error "wrong number of digits"
  |> Result.bind (fun d ->
    if d.[0] = '0' || d.[0] = '1' then Error "invalid area code"
    else Ok d)
  |> Result.bind (fun d ->
    if d.[3] = '0' || d.[3] = '1' then Error "invalid exchange"
    else Ok d)

let () =
  assert (validate "(223) 456-7890" = Ok "2234567890");
  assert (validate "(023) 456-7890" = Error "invalid area code")

📊 Detailed Comparison

Phone Number Parser — Comparison

Core Insight

Validation pipelines demonstrate monadic chaining with Result. Both OCaml (`Result.bind`) and Rust (`.and_then()`) thread a value through a series of checks, short-circuiting on the first error.

OCaml Approach

  • `Result.bind` chains validations: `Ok x |> Result.bind f1 |> Result.bind f2`
  • `String.to_seq |> Seq.filter |> String.of_seq` for digit extraction
  • `d.[0]` for character access
  • `Error "message"` for error variants

Rust Approach

  • `.and_then(f)` chains validations (same as `Result.bind`)
  • `.chars().filter().collect()` for digit extraction
  • `d.as_bytes()[0]` for byte access, `d.starts_with()` for prefix
  • `Err("message")` with static string slices

Comparison Table

AspectOCamlRust
Bind`Result.bind``.and_then()`
Filter chars`Seq.filter``.chars().filter()`
String access`s.[i]` (char)`s.as_bytes()[i]` (u8)
Error type`string``&'static str`
Substring`String.sub d 1 10``d[1..].to_string()`

Learner Notes

  • Rust's `?` operator is syntactic sugar for and_then in many cases
  • `and_then` passes ownership of the Ok value to the next function
  • OCaml's `|>` pipe and Rust's method chaining serve the same purpose
  • Both approaches avoid nested if/else by linearizing validation steps