Applying a Function Twice
Tutorial Video
Text description (accessibility)
This video demonstrates the "Applying a Function Twice" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Higher-Order Functions, Functional Composition, Currying. Given a function `f` and a value `x`, apply the function `f` to `x`, and then apply `f` again to the result. Key difference from OCaml: 1. **Currying:** OCaml's automatic currying means `twice double` is syntactic sugar for `(twice double)`. In Rust, we write `|x| twice(double, x)` or use `twice_compose` to achieve similar partial application.
Tutorial
The Problem
Given a function f and a value x, apply the function f to x, and then apply f again to the result. This is a fundamental pattern in functional programming that demonstrates how functions can be treated as first-class values.
🎯 Learning Outcomes
🦀 The Rust Way
Rust requires explicit handling of function types through:
impl Fn(T) -> T captures any callable typefn(T) -> T for concrete function referencesWe provide three implementations that grow in expressiveness:
twice** — accepts any callable (closure or function) via trait boundtwice_fn** — accepts explicit function pointerstwice_compose** — returns a new closure that can be stored and reusedCode Example
pub fn twice<T>(f: impl Fn(T) -> T, x: T) -> T {
f(f(x))
}
pub fn double(x: i32) -> i32 {
2 * x
}
pub fn square(x: i32) -> i32 {
x * x
}
fn main() {
let quad = |x| twice(double, x);
let fourth = |x| twice(square, x);
println!("quad 3 = {}", quad(3)); // 12
println!("fourth 2 = {}", fourth(2)); // 16
}Key Differences
twice double is syntactic sugar for (twice double). In Rust, we write |x| twice(double, x) or use twice_compose to achieve similar partial application.'a -> 'b. Rust distinguishes between concrete function pointers fn(T) -> T and closures captured via impl Fn(T) -> T, giving more control but requiring explicit choices.impl Fn(T) -> T trait bound is more flexible than a bare function pointer, accepting closures with captured state.twice_compose returns a closure, enabling functional composition patterns. OCaml achieves this naturally through function composition.OCaml Approach
OCaml's twice function is elegantly simple:
let twice f x = f (f x)
This takes advantage of OCaml's currying: f and x are separate parameters, and partial application is implicit. You can write let quad = twice double to bind double as the function argument, creating a partially-applied function.
Full Source
#![allow(clippy::all)]
/// Apply a function twice to a value.
/// Demonstrates higher-order functions and partial application.
///
/// Takes a function `f` and a value `x`, applies `f` to `x`, then applies `f` again.
pub fn twice<T>(f: impl Fn(T) -> T, x: T) -> T {
f(f(x))
}
/// Alternative using function pointers (more explicit type).
/// Useful when you need to pass function pointers directly.
pub fn twice_fn<T: Copy>(f: fn(T) -> T, x: T) -> T {
f(f(x))
}
/// Functional approach: compose a function with itself.
/// Returns a closure that applies the original function twice.
pub fn twice_compose<T: 'static>(f: impl Fn(T) -> T + 'static) -> impl Fn(T) -> T {
move |x| f(f(x))
}
// Helper functions for examples
pub fn double(x: i32) -> i32 {
2 * x
}
pub fn square(x: i32) -> i32 {
x * x
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_twice_double_zero() {
assert_eq!(twice(double, 0), 0);
}
#[test]
fn test_twice_double_positive() {
// double(3) = 6, double(6) = 12
assert_eq!(twice(double, 3), 12);
}
#[test]
fn test_twice_square_small() {
// square(2) = 4, square(4) = 16
assert_eq!(twice(square, 2), 16);
}
#[test]
fn test_twice_square_one() {
assert_eq!(twice(square, 1), 1);
}
#[test]
fn test_twice_fn_double() {
assert_eq!(twice_fn(double, 3), 12);
}
#[test]
fn test_twice_fn_square() {
assert_eq!(twice_fn(square, 2), 16);
}
#[test]
fn test_twice_compose_double() {
let quad = twice_compose(double);
assert_eq!(quad(3), 12);
}
#[test]
fn test_twice_compose_square() {
let fourth = twice_compose(square);
assert_eq!(fourth(2), 16);
}
#[test]
fn test_twice_with_closure() {
let add_five = |x| x + 5;
assert_eq!(twice(add_five, 0), 10);
}
#[test]
fn test_twice_negative() {
assert_eq!(twice(double, -3), -12);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_twice_double_zero() {
assert_eq!(twice(double, 0), 0);
}
#[test]
fn test_twice_double_positive() {
// double(3) = 6, double(6) = 12
assert_eq!(twice(double, 3), 12);
}
#[test]
fn test_twice_square_small() {
// square(2) = 4, square(4) = 16
assert_eq!(twice(square, 2), 16);
}
#[test]
fn test_twice_square_one() {
assert_eq!(twice(square, 1), 1);
}
#[test]
fn test_twice_fn_double() {
assert_eq!(twice_fn(double, 3), 12);
}
#[test]
fn test_twice_fn_square() {
assert_eq!(twice_fn(square, 2), 16);
}
#[test]
fn test_twice_compose_double() {
let quad = twice_compose(double);
assert_eq!(quad(3), 12);
}
#[test]
fn test_twice_compose_square() {
let fourth = twice_compose(square);
assert_eq!(fourth(2), 16);
}
#[test]
fn test_twice_with_closure() {
let add_five = |x| x + 5;
assert_eq!(twice(add_five, 0), 10);
}
#[test]
fn test_twice_negative() {
assert_eq!(twice(double, -3), -12);
}
}
Deep Comparison
OCaml vs Rust: Applying a Function Twice
Side-by-Side Code
OCaml
let twice f x = f (f x)
let double x = 2 * x
let square x = x * x
let quad = twice double (* applies double twice *)
let fourth = twice square (* applies square twice *)
let () =
Printf.printf "quad 3 = %d\n" (quad 3); (* 12 *)
Printf.printf "fourth 2 = %d\n" (fourth 2) (* 16 *)
Rust (idiomatic — using closures and trait bounds)
pub fn twice<T>(f: impl Fn(T) -> T, x: T) -> T {
f(f(x))
}
pub fn double(x: i32) -> i32 {
2 * x
}
pub fn square(x: i32) -> i32 {
x * x
}
fn main() {
let quad = |x| twice(double, x);
let fourth = |x| twice(square, x);
println!("quad 3 = {}", quad(3)); // 12
println!("fourth 2 = {}", fourth(2)); // 16
}
Rust (functional — composition-based)
pub fn twice_compose<T: 'static>(f: impl Fn(T) -> T + 'static) -> impl Fn(T) -> T {
move |x| f(f(x))
}
fn main() {
let quad_composed = twice_compose(double);
let fourth_composed = twice_compose(square);
println!("quad_composed 3 = {}", quad_composed(3)); // 12
println!("fourth_composed 2 = {}", fourth_composed(2)); // 16
}
Type Signatures
| Concept | OCaml | Rust |
|---|---|---|
| Function type | ('a -> 'a) -> 'a -> 'a | fn twice<T>(f: impl Fn(T) -> T, x: T) -> T |
| Partial application | let quad = twice double (automatic) | \|x\| twice(double, x) (explicit closure) |
| Closure type | 'a -> 'a (implicit) | impl Fn(T) -> T (trait bound) or fn(T) -> T (function pointer) |
| Returned closure | Implicit via type inference | Explicit via impl Fn(T) -> T or named Box<dyn Fn> |
Key Insights
twice double as a partial application. In Rust, we must explicitly create a closure |x| twice(double, x) to achieve the same effect. This reflects Rust's principle of making ownership and lifetimes explicit.impl Fn(T) -> T accepts any callable (function pointer, closure with captured variables, method reference). OCaml lumps everything into function types but forces you to think about captures differently.twice_compose variant requires T: 'static when returning a closure because the closure needs to own the captured function. In OCaml, this complexity is hidden by the garbage collector. - twice with impl Fn — most flexible, works with any callable
- twice_fn with function pointers — explicit, predictable, no allocations
- twice_compose returning a closure — enables function composition chains
When to Use Each Style
Use idiomatic Rust when: You're building APIs that need to accept various callables (functions, closures, method references). The impl Fn bound is the Swiss Army knife of function passing.
Use function pointers when: You need maximum performance and predictability (no allocations, no closures on the heap). Function pointers are zero-cost abstractions.
**Use closure composition (twice_compose) when:** You're building functional programming libraries where composing functions is a core operation. This style mirrors OCaml idioms most closely.
Performance Characteristics
twice:** Zero-cost abstraction; inlining works naturally.twice with impl Fn:** Zero-cost when the function is a concrete type (monomorphized), but the generic specialization happens at compile time.twice_fn:** Guaranteed zero-cost; function pointers are thin pointers with no captures.twice_compose returning closure:** May allocate if the closure is boxed or captured across function boundaries. If stack-allocated, still zero-cost.The bottom line: Rust's generics are as efficient as OCaml's polymorphism, but require explicit attention to allocation and ownership.
Exercises
thrice (apply a function three times) in both languages|> in Rust that mimics OCaml's |> pipelineapply_n_times function that applies a function n times