๐Ÿฆ€ Functional Rust

051: Applying a Function Twice

Difficulty: 1 Level: Beginner Pass a function as a value and apply it twice: `twice(f, x) = f(f(x))`.

The Problem This Solves

Most languages let you call functions, but can you pass a function to another function? Can you return a function from a function? These are the hallmarks of higher-order functions โ€” the foundation of functional programming. Without higher-order functions, you'd write `double(double(x))` and `square(square(x))` as separate concrete functions. With `twice`, you write the pattern once and parameterize over the function. The same logic works for any transformation you want to apply twice: encryption rounds, normalisation steps, geometric transforms. This example is deliberately tiny โ€” `twice(f, x) = f(f(x))` โ€” so the mechanics of higher-order functions are visible without distracting complexity.

The Intuition

Think of `twice` like a recipe instruction: "do this step, then do it again." The recipe doesn't care what the step is. It just applies it twice. `twice(stir, bowl)` stirs twice; `twice(double, 3)` gives 12. Partial application goes one step further: `twice_partial(double)` bakes "double" into a new function `quad` that you can call later with any number. `quad(3)` gives 12, `quad(5)` gives 20 โ€” the step is frozen, only the input varies.

How It Works in Rust

// Generic: F must be Fn(T)->T so it can be called multiple times
pub fn twice<T, F: Fn(T) -> T>(f: F, x: T) -> T {
 f(f(x))  // first call produces intermediate, second consumes it
}

// Partial application: bind f, return a new closure
pub fn twice_partial<T, F: Fn(T) -> T>(f: F) -> impl Fn(T) -> T {
 move |x| f(f(x))  // f is moved into the closure; safe to call many times
}

pub fn double(x: i32) -> i32 { 2 * x }
pub fn square(x: i32) -> i32 { x * x }

// Usage:
let quad   = twice_partial(double);  // quad(3) == 12
let fourth = twice_partial(square);  // fourth(2) == 16
`Fn(T) -> T` (not `FnOnce`) means the closure can be called repeatedly. The `move` keyword transfers ownership of `f` into the returned closure.

What This Unlocks

Key Differences

ConceptOCamlRust
Partial applicationBuilt-in: `let quad = twice double`Explicit closure: `twice_partial(double)`
Function type`('a -> 'a) -> 'a -> 'a``fn<T, F: Fn(T)->T>(f: F, x: T) -> T`
Returning a functionCurrying is automatic`impl Fn(T) -> T` return type
Capturing in closureAutomatic under GC`move` keyword required
Function pointersNo distinction`fn(i32) -> i32` (no captures, zero overhead)
//! # Applying a Function Twice
//! OCaml CS3110 โ€” Higher-order function that applies a function twice,
//! demonstrating currying and partial application.

// โ”€โ”€ Implementation 1: Idiomatic Rust โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
//
// Generic over the argument type T and any function F that maps T โ†’ T.
// `F: Fn(T) -> T` means f can be called repeatedly (shared reference semantics).
// Ownership: x is moved in, the intermediate result of f(x) is moved into f(โ€ฆ).

/// Apply `f` to `x` twice: `f(f(x))`.
pub fn twice<T, F>(f: F, x: T) -> T
where
    F: Fn(T) -> T,
{
    f(f(x))
}

// โ”€โ”€ Implementation 2: Partial application (curried style) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
//
// Returns a closure that captures `f` by move.
// This matches OCaml's `let quad = twice double` โ€” bind the function,
// get back a new function waiting for the argument.
//
// `impl Fn(T) -> T` in return position: the compiler knows the concrete
// closure type; we hide it behind `impl Trait` so callers stay generic.

/// Partially apply `twice`: bind `f`, return a new `Fn(T) -> T`.
///
/// Example: `let quad = twice_partial(double);` โ€” then `quad(3) == 12`.
pub fn twice_partial<T, F>(f: F) -> impl Fn(T) -> T
where
    F: Fn(T) -> T,
{
    // `f` is moved into the closure.  Because F: Fn (not FnOnce), calling
    // the closure multiple times is safe โ€” f itself is never consumed.
    move |x| f(f(x))
}

// โ”€โ”€ Implementation 3: Function-pointer variant โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
//
// `fn(i32) -> i32` is a bare function pointer, not a closure.
// This is less general (no captured environment) but zero-overhead and
// useful when you only need named free functions like `double` / `square`.

/// Apply a bare function pointer twice (no closure capture).
pub fn twice_fp(f: fn(i32) -> i32, x: i32) -> i32 {
    f(f(x))
}

// โ”€โ”€ Named helpers matching the OCaml originals โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

pub fn double(x: i32) -> i32 {
    2 * x
}

pub fn square(x: i32) -> i32 {
    x * x
}

// โ”€โ”€ Tests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

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

    // --- twice (generic) ---

    #[test]
    fn test_twice_double() {
        // double(double(3)) = double(6) = 12
        assert_eq!(twice(double, 3), 12);
    }

    #[test]
    fn test_twice_square() {
        // square(square(2)) = square(4) = 16
        assert_eq!(twice(square, 2), 16);
    }

    #[test]
    fn test_twice_with_closure() {
        // Works with any Fn(T) -> T, including closures
        let increment = |x: i32| x + 1;
        assert_eq!(twice(increment, 5), 7);
    }

    #[test]
    fn test_twice_identity() {
        assert_eq!(twice(|x: i32| x, 42), 42);
    }

    // --- twice_partial (curried / partial application) ---

    #[test]
    fn test_partial_quad() {
        // quad = twice double โ€” bind double, get a new function
        let quad = twice_partial(double);
        assert_eq!(quad(3), 12);
        assert_eq!(quad(0), 0);
        assert_eq!(quad(-1), -4);
    }

    #[test]
    fn test_partial_fourth() {
        // fourth = twice square โ€” x^4
        let fourth = twice_partial(square);
        assert_eq!(fourth(2), 16);
        assert_eq!(fourth(3), 81);
    }

    #[test]
    fn test_partial_reusable() {
        // The returned closure can be called multiple times
        let quad = twice_partial(|x: i32| 2 * x);
        let results: Vec<i32> = (1..=4).map(|x| quad(x)).collect();
        assert_eq!(results, vec![4, 8, 12, 16]);
    }

    // --- twice_fp (function pointer) ---

    #[test]
    fn test_fp_double() {
        assert_eq!(twice_fp(double, 3), 12);
    }

    #[test]
    fn test_fp_square() {
        assert_eq!(twice_fp(square, 2), 16);
    }
}

fn main() {
    println!("{:?}", twice(double, 3), 12);
    println!("{:?}", twice(square, 2), 16);
    println!("{:?}", twice(increment, 5), 7);
}
let twice f x = f (f x)

let double x = 2 * x
let square x = x * x

let quad   = twice double
let fourth = twice square

let () =
  Printf.printf "quad 3   = %d\n" (quad 3);
  Printf.printf "fourth 2 = %d\n" (fourth 2)

๐Ÿ“Š Detailed Comparison

Side-by-Side: Applying a Function Twice

OCaml

๐Ÿช Show OCaml equivalent
let twice f x = f (f x)

let double x = 2 * x
let square x = x * x

let quad   = twice double   (* partial application โ€” no argument given *)
let fourth = twice square

let () =
Printf.printf "quad 3   = %d\n" (quad 3);    (* 12 *)
Printf.printf "fourth 2 = %d\n" (fourth 2)   (* 16 *)

Rust โ€” Implementation 1: Generic (direct form)

pub fn twice<T, F>(f: F, x: T) -> T
where
 F: Fn(T) -> T,
{
 f(f(x))
}

// Usage (both arguments supplied):
twice(double, 3)   // 12
twice(square, 2)   // 16

Rust โ€” Implementation 2: Partial application (curried style)

pub fn twice_partial<T, F>(f: F) -> impl Fn(T) -> T
where
 F: Fn(T) -> T,
{
 move |x| f(f(x))  // f captured by move into the returned closure
}

// Usage โ€” mirrors OCaml exactly:
let quad   = twice_partial(double);   // quad: impl Fn(i32) -> i32
let fourth = twice_partial(square);

quad(3)     // 12
fourth(2)   // 16

Rust โ€” Implementation 3: Function pointer variant

pub fn twice_fp(f: fn(i32) -> i32, x: i32) -> i32 {
 f(f(x))
}

// fn(i32) -> i32 โ€” bare function pointer, no captured environment.
twice_fp(double, 3)   // 12
twice_fp(square, 2)   // 16

---

Concept Map

OCaml conceptRust equivalent
Curried function `('a -> 'a) -> 'a -> 'a`Two-argument generic fn or `impl Fn` return
`let quad = twice double``let quad = twice_partial(double)`
Polymorphic `'a`Generic type parameter `T`
All functions implicitly curriedClosures capture by move; explicit `impl Fn(T) -> T`
Function value (no distinction)`Fn` trait (closure) vs `fn` pointer

Why `Fn` and not `FnOnce`?

`FnOnce` means the closure can only be called once (it consumes captured values). Here `f` is called twice inside the body, so it must be `Fn` (callable by shared reference, any number of times). `FnMut` would also work but is unnecessarily broad when no mutation is needed.

Ownership note on `twice_partial`

// f is moved into the returned closure.
// The closure itself is Fn because F: Fn โ€” it can be called many times.
// Each call to the returned closure borrows f immutably to invoke f(f(x)).
move |x| f(f(x))

The intermediate value `f(x): T` is owned by the stack frame and then moved into the second call `f(โ€ฆ)`. No heap allocation is required.