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

525: Tap Pattern for Side Effects

Difficulty: 2 Level: Beginner-Intermediate Insert logging, debugging, or instrumentation into a pipeline without breaking the data flow.

The Problem This Solves

You have a clean functional pipeline: `data.iter().filter(pred).map(transform).collect()`. You need to debug it โ€” but adding a `println!` breaks the chain. You'd have to split the pipeline into separate `let` bindings, add the print, then continue. Now your clean one-liner is four lines. The same problem in production: you want to log intermediate values, emit metrics, or trigger side effects at specific points in a transformation chain without restructuring the chain or touching the data flowing through it. Without tap, debugging a functional pipeline requires either restructuring it (breaking the flow) or switching to an imperative loop (losing the composition benefits).

The Intuition

Tap is the functional programmer's `println!` that doesn't interrupt the flow. It's inspired by the Unix `tee` command โ€” pipe data through, copy it somewhere else, let the original flow continue untouched. `tap(value, |v| println!("{:?}", v))` returns `value` unchanged after running the closure. The value flows through as if tap wasn't there; the side effect happens invisibly. In Ruby, `.tap` is a built-in method on every object. In JavaScript and Python, you'd write `(x => { console.log(x); return x; })(value)` โ€” a tap lambda. Rust's extension trait approach gives you `.tap(|v| ...)` as a method on any type.

How It Works in Rust

// Free function version: run side effect, return value unchanged
fn tap<T, F: FnOnce(&T)>(value: T, f: F) -> T {
 f(&value);   // side effect โ€” observe but don't consume
 value        // return original
}

// Extension trait: adds .tap() to EVERY type
trait Tap: Sized {
 fn tap(self, f: impl FnOnce(&Self)) -> Self {
     f(&self);
     self
 }
 fn tap_mut(mut self, f: impl FnOnce(&mut Self)) -> Self {
     f(&mut self);
     self
 }
 fn tap_if(self, condition: bool, f: impl FnOnce(&Self)) -> Self {
     if condition { f(&self); }
     self
 }
}
impl<T> Tap for T {}   // blanket impl: works on all types

// Usage in a transformation chain
let result = vec![3, 1, 4, 1, 5, 9, 2, 6]
 .tap(|v| println!("input: {:?}", v))      // observe raw input
 .tap_mut(|v| v.sort())                    // sort in place
 .tap(|v| println!("sorted: {:?}", v))     // observe sorted
 .tap_mut(|v| v.dedup())                   // deduplicate
 .tap(|v| println!("deduped: {:?}", v));   // observe deduped

// In an iterator pipeline โ€” map acts as tap for individual elements
let sum: i32 = (1..=5)
 .map(|x| tap(x, |v| print!("in:{} ", v)))     // observe: before filter
 .filter(|&x| x % 2 != 0)                       // keep odds
 .map(|x| tap(x * x, |v| print!("sq:{} ", v))) // observe: after squaring
 .sum();

// Conditional tap โ€” only active in debug builds
let debug = cfg!(debug_assertions);
let x = compute_value()
 .tap_if(debug, |v| log::debug!("value: {:?}", v));
The `FnOnce` bound (not `Fn`) reflects that tap is called exactly once per value โ€” this is the minimum required bound, accepting the widest range of closures.

What This Unlocks

Key Differences

ConceptOCamlRust
Tap`let () = f x in x``x.tap(\v\side_effect(v))`
Extension methodModule-level functionBlanket `impl<T> Tap for T`
Mutable tapRebind with `let``.tap_mut(\v\v.sort())` โ€” in place
Conditional`if debug then f x``.tap_if(condition, f)`
Iterator tapMap then ignore result`.map(\x\tap(x, inspect))`
//! # 525. Tap Pattern for Side Effects
//! Inspect values in a pipeline without disrupting the data flow.

/// Tap: run a side effect, then return the value unchanged
fn tap<T, F: FnOnce(&T)>(value: T, f: F) -> T {
    f(&value);
    value
}

/// Tap with a mutable reference (for accumulating taps)
fn tap_mut<T, F: FnOnce(&mut T)>(mut value: T, f: F) -> T {
    f(&mut value);
    value
}

/// Extension trait to enable chained .tap() calls
trait Tap: Sized {
    fn tap(self, f: impl FnOnce(&Self)) -> Self {
        f(&self);
        self
    }

    fn tap_mut(mut self, f: impl FnOnce(&mut Self)) -> Self {
        f(&mut self);
        self
    }

    fn tap_if(self, condition: bool, f: impl FnOnce(&Self)) -> Self {
        if condition { f(&self); }
        self
    }
}

// Implement Tap for all types
impl<T> Tap for T {}

fn main() {
    // Basic tap in a transformation chain
    let result = 5i32
        .tap(|&x| println!("initial: {}", x))
        .tap(|x| println!("before double: {}", x));
    let result = result * 2;
    let result = result
        .tap(|x| println!("after double: {}", x));
    let result = result + 1;
    let result = result
        .tap(|x| println!("after inc: {}", x));
    let result = result * result;
    println!("final: {}", result);

    // Tap in iterator pipeline (using map as tap)
    println!("\nIterator pipeline with taps:");
    let sum: i32 = (1..=5)
        .map(|x| tap(x, |v| print!("in:{} ", v)))
        .filter(|&x| x % 2 != 0)
        .map(|x| tap(x * x, |v| print!("sq:{} ", v)))
        .sum();
    println!("\nsum of odd squares: {}", sum);

    // Tap with Vec โ€” inspect intermediate state
    let data = vec![3, 1, 4, 1, 5, 9, 2, 6];
    let result: Vec<i32> = data.into_iter()
        .tap(|_| println!("\nProcessing..."))  // wait, tap is on T not Iterator
        .collect::<Vec<_>>()
        .tap(|v| println!("collected {} items", v.len()))
        .tap_mut(|v| v.sort())
        .tap(|v| println!("sorted: {:?}", v))
        .tap_mut(|v| v.dedup())
        .tap(|v| println!("deduped: {:?}", v));

    println!("final: {:?}", result);

    // Conditional tap (only in debug mode)
    let debug = true;
    let x = 42i32
        .tap_if(debug, |v| println!("debug value: {}", v));
    println!("x = {}", x);
}

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

    #[test]
    fn test_tap_returns_value() {
        let result = tap(42, |_| {});
        assert_eq!(result, 42);
    }

    #[test]
    fn test_tap_runs_side_effect() {
        let mut called = false;
        let _ = tap(10, |_| { called = true; });
        assert!(called);
    }

    #[test]
    fn test_tap_trait() {
        let result = 5i32
            .tap(|_| {})
            .tap_mut(|x| *x *= 2);
        assert_eq!(result, 10);
    }

    #[test]
    fn test_tap_if() {
        let mut log = Vec::new();
        let _ = 7i32.tap_if(true, |_| log.push("logged"));
        assert_eq!(log, ["logged"]);
        let _ = 7i32.tap_if(false, |_| log.push("should not log"));
        assert_eq!(log.len(), 1);
    }
}
(* Tap pattern in OCaml *)
let tap f x = f x; x

(* Debugging pipeline *)
let debug label x =
  Printf.printf "[%s]: %s\n" label (string_of_int x);
  x

let () =
  let result =
    5
    |> (fun x -> x * 2)
    |> tap (fun x -> Printf.printf "after double: %d\n" x)
    |> (fun x -> x + 1)
    |> tap (fun x -> Printf.printf "after inc: %d\n" x)
    |> (fun x -> x * x)
  in
  Printf.printf "result: %d\n" result;

  let nums = [1;2;3;4;5] in
  let sum = List.fold_left (fun acc x ->
    tap (fun s -> Printf.printf "running sum: %d\n" s) (acc + x)
  ) 0 nums in
  Printf.printf "final sum: %d\n" sum