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
- Composable transformations โ apply any `Fn(T) -> T` twice without rewriting it: encryption rounds, string normalisation, numeric iteration.
- Partial application pattern โ freezing one argument to build specialized functions; the Rust idiom for what OCaml does automatically with currying.
- Understanding `Fn`/`FnMut`/`FnOnce` โ `twice` needs `Fn` (not `FnOnce`) because `f` is called twice. Recognizing which trait bound you need is a core Rust skill.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| Partial application | Built-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 function | Currying is automatic | `impl Fn(T) -> T` return type |
| Capturing in closure | Automatic under GC | `move` keyword required |
| Function pointers | No 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) // 16Rust โ 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) // 16Rust โ 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 concept | Rust 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 curried | Closures 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.