๐Ÿฆ€ Functional Rust
๐ŸŽฌ Closures in Rust Fn/FnMut/FnOnce, capturing environment, move closures, higher-order functions.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Closures capture variables from their environment โ€” by reference, mutable reference, or by value (move)

โ€ข Three traits: Fn (shared borrow), FnMut (mutable borrow), FnOnce (takes ownership)

โ€ข Higher-order functions like .map(), .filter(), .fold() accept closures as arguments

โ€ข move closures take ownership of captured variables โ€” essential for threading

โ€ข Closures enable functional patterns: partial application, composition, and strategy

522: Predicate Functions Pattern

Difficulty: 2 Level: Beginner-Intermediate Compose boolean tests from smaller predicates using `and`, `or`, and `not`.

The Problem This Solves

Filtering logic in real code gets complex fast: "items that are positive, even, and less than 100" or "strings that start with 'h' and are longer than 3 characters." Writing these as inline closures works but doesn't scale. The boolean conditions can't be reused, tested independently, or composed in new combinations without copy-pasting. The predicate combinator pattern solves this: each predicate is a `Fn(&T) -> bool`, and you build combinators like `pred_and`, `pred_or`, and `pred_not` that take two predicates and return a new one. The resulting predicates are closures that close over the originals, so they compose freely. You can build a library of named predicates and combine them at the call site. This is the Rust equivalent of OCaml's function composition and Haskell's predicate combinators. It's a practical example of functional style in production Rust code.

The Intuition

A predicate is just a function from a value to a boolean. Combining predicates is combining functions. `pred_and(p1, p2)` returns a new closure that calls both and `&&`s the results. Since closures can capture other closures, this composes indefinitely. `all_of(vec![p1, p2, p3])` returns a closure that iterates the list and short-circuits.

How It Works in Rust

1. Basic predicate โ€” `let is_even = |x: &i32| x % 2 == 0;` โ€” a `Fn(&i32) -> bool`. 2. `pred_and` โ€” `fn pred_and<T, P1: Fn(&T)->bool, P2: Fn(&T)->bool>(p1: P1, p2: P2) -> impl Fn(&T)->bool { move |x| p1(x) && p2(x) }`. 3. `all_of` โ€” takes `Vec<Box<dyn Fn(&T)->bool>>`; returns closure that calls `.iter().all(|p| p(x))`. 4. Use with `filter` โ€” `nums.iter().filter(|x| is_valid(x))` โ€” the composed predicate slots directly into iterator adapters. 5. Negation โ€” `pred_not(p)` returns `move |x| !p(x)`; negating any predicate produces a new predicate.

What This Unlocks

Key Differences

ConceptOCamlRust
Predicate composition`let both f g x = f x && g x` โ€” naturalSame; `pred_and(p1, p2)` returns `impl Fn` closure
Higher-order predicatesFirst-class; no boxing needed`impl Fn` for generics; `Box<dyn Fn>` for heterogeneous lists
`all_of` / `any_of``List.for_all`, `List.exists` with predicate list`all_of(Vec<Box<dyn Fn>>)` via `.iter().all(...)`
Use in filter`List.filter pred``.iter().filter(xpred(x))`
//! # 522. Predicate Functions Pattern
//! Composable predicates: and, or, not, all_of, any_of.

/// Combine two predicates with AND
fn pred_and<T, P1, P2>(p1: P1, p2: P2) -> impl Fn(&T) -> bool
where
    P1: Fn(&T) -> bool,
    P2: Fn(&T) -> bool,
{
    move |x| p1(x) && p2(x)
}

/// Combine two predicates with OR
fn pred_or<T, P1, P2>(p1: P1, p2: P2) -> impl Fn(&T) -> bool
where
    P1: Fn(&T) -> bool,
    P2: Fn(&T) -> bool,
{
    move |x| p1(x) || p2(x)
}

/// Negate a predicate
fn pred_not<T, P>(p: P) -> impl Fn(&T) -> bool
where
    P: Fn(&T) -> bool,
{
    move |x| !p(x)
}

/// All predicates must hold
fn all_of<T>(predicates: Vec<Box<dyn Fn(&T) -> bool>>) -> impl Fn(&T) -> bool {
    move |x| predicates.iter().all(|p| p(x))
}

/// At least one predicate must hold
fn any_of<T>(predicates: Vec<Box<dyn Fn(&T) -> bool>>) -> impl Fn(&T) -> bool {
    move |x| predicates.iter().any(|p| p(x))
}

fn main() {
    let is_even     = |x: &i32| x % 2 == 0;
    let is_positive = |x: &i32| *x > 0;
    let is_small    = |x: &i32| *x < 100;

    let is_valid = all_of(vec![
        Box::new(is_even),
        Box::new(is_positive),
        Box::new(is_small),
    ]);

    let nums = vec![-2, 0, 4, 8, 102, -6, 50, 99];
    let valid: Vec<_> = nums.iter().filter(|x| is_valid(x)).collect();
    println!("valid (even, positive, <100): {:?}", valid);

    let extreme = pred_or(|x: &i32| *x < 0, |x: &i32| *x > 50);
    let extremes: Vec<_> = nums.iter().filter(|&&ref x| extreme(x)).collect();
    println!("extreme (<0 or >50): {:?}", extremes);

    // Negation
    let is_odd = pred_not(|x: &i32| x % 2 == 0);
    let odds: Vec<_> = nums.iter().filter(|x| is_odd(x)).collect();
    println!("odds: {:?}", odds);

    // String predicates
    let words = vec!["hello", "hi", "hey", "world", "rust", "hat"];
    let starts_h = |w: &&str| w.starts_with('h');
    let long = |w: &&str| w.len() > 3;
    let h_and_long: Vec<_> = words.iter().filter(|w| starts_h(w) && long(w)).collect();
    println!("starts with 'h' AND len>3: {:?}", h_and_long);
}

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

    #[test]
    fn test_pred_and() {
        let f = pred_and(|x: &i32| *x > 0, |x: &i32| *x < 10);
        assert!(f(&5));
        assert!(!f(&0));
        assert!(!f(&10));
    }

    #[test]
    fn test_pred_or() {
        let f = pred_or(|x: &i32| *x < 0, |x: &i32| *x > 100);
        assert!(f(&-1));
        assert!(f(&200));
        assert!(!f(&50));
    }

    #[test]
    fn test_pred_not() {
        let is_even = |x: &i32| x % 2 == 0;
        let is_odd = pred_not(is_even);
        assert!(is_odd(&3));
        assert!(!is_odd(&4));
    }

    #[test]
    fn test_all_of() {
        let pred = all_of(vec![
            Box::new(|x: &i32| *x > 0),
            Box::new(|x: &i32| *x < 100),
            Box::new(|x: &i32| x % 2 == 0),
        ]);
        assert!(pred(&42));
        assert!(!pred(&-2));
        assert!(!pred(&101));
    }
}
(* Predicate combinators in OCaml *)
let pred_and p1 p2 x = p1 x && p2 x
let pred_or  p1 p2 x = p1 x || p2 x
let pred_not p x = not (p x)

let () =
  let is_even x = x mod 2 = 0 in
  let is_positive x = x > 0 in
  let is_small x = x < 100 in

  let is_valid = pred_and (pred_and is_even is_positive) is_small in
  let nums = [-2; 0; 4; 8; 102; -6; 50; 99] in
  let valid = List.filter is_valid nums in
  Printf.printf "valid (even, positive, <100): [%s]\n"
    (String.concat "; " (List.map string_of_int valid));

  let either = pred_or (fun x -> x < 0) (fun x -> x > 50) in
  let extreme = List.filter either nums in
  Printf.printf "extreme (<0 or >50): [%s]\n"
    (String.concat "; " (List.map string_of_int extreme))