BasicsImpCodeStory_Lambdas.html
copyright © James Fawcett
Revised: 05/11/2026
6.0 Prologue
A lambda (anonymous function) is a function defined inline without a
name. A closure is a lambda that captures variables from its enclosing
scope. Closures enable passing behavior as data, deferring computation, and building
iterators and callbacks without polluting the namespace with single-use named functions.
6.1 Anonymous Functions
Each language has a distinct syntax for inline function literals:
// Rust |params| body or |params| { block }
let double = |x: i32| x * 2;
let add = |a, b| { a + b }; // types inferred from usage
// C++ [capture](params) -> ret { body }
auto double = [](int x) { return x * 2; };
auto add = [](int a, int b) -> int { return a + b; };
// C# (params) => expr or (params) => { block }
Func<int,int> double = x => x * 2;
Func<int,int,int> add = (a, b) => a + b;
// Python lambda params: expr (expression-only body)
double = lambda x: x * 2
add = lambda a, b: a + b
Python lambdas are restricted to a single expression; multi-statement logic requires a
named function. The other three languages allow full block bodies in their lambda forms.
6.2 Capturing Environment
A closure captures variables from the enclosing scope. The capture mode determines
whether the closure borrows, copies, or moves the captured value.
// Rust: capture mode inferred; move forces ownership transfer
let threshold = 10;
let above = |x| x > threshold; // borrows threshold
let msg = String::from("hello");
let print_msg = move || println!("{msg}"); // moves msg into closure
// C++: capture list controls the mode
int threshold = 10;
auto above = [threshold](int x) { return x > threshold; }; // copy
auto above = [&threshold](int x) { return x > threshold; }; // reference
auto above = [=](int x) { return x > threshold; }; // all by copy
auto above = [&](int x) { return x > threshold; }; // all by ref
// C# and Python: always capture by reference (the variable binding, not the value)
int threshold = 10;
Func<int,bool> above = x => x > threshold; // C#: captures the variable
threshold = 20; // above now uses 20
Rust’s borrow checker enforces that captured references do not outlive the closure.
C++ offers no such guarantee - a closure capturing a reference to a local variable
and escaping the local’s scope is undefined behavior.
6.3 Closure Types
Rust models closure capabilities as three traits:
| Trait |
Meaning |
Can be called |
Fn |
borrows captures immutably |
any number of times |
FnMut |
borrows captures mutably |
any number of times (requires mut) |
FnOnce |
consumes captured values |
exactly once |
Every closure implements at least FnOnce. If it does not consume its
captures it also implements FnMut; if it does not mutate them it also
implements Fn. Function parameters accepting closures use these as bounds:
fn apply(f: impl Fn(i32) -> i32).
In C++, lambdas are anonymous struct types with an operator(). They are
not standardly polymorphic across different lambda types; std::function<T>
provides a type-erased wrapper at the cost of a heap allocation and virtual dispatch.
In C#, delegates and Func<>/Action<> serve the
same role. Python functions are first-class objects and are inherently polymorphic.
6.4 Function Pointers vs Closures
A function pointer holds the address of a named function (no captured
state). It is a single pointer-size value and incurs no heap allocation.
A closure may carry captured state alongside the code pointer.
// Rust
fn square(x: i32) -> i32 { x * x }
let f: fn(i32) -> i32 = square; // function pointer, no capture
let g = |x: i32| x * x; // closure (zero-capture, also fn-pointer compatible)
// C++
int (*f)(int) = square; // C-style function pointer
auto g = [](int x) { return x*x; }; // non-capturing lambda (convertible to fn ptr)
// C#
Func<int,int> f = x => x * x; // delegate; always heap-allocated
In Rust, a non-capturing closure is zero-sized and can be coerced to a function pointer.
A capturing closure has non-zero size and cannot. Using impl Fn (static
dispatch, monomorphized) avoids heap allocation; using Box<dyn Fn>
(dynamic dispatch) allows heterogeneous collections of closures.
6.5 Epilogue
Closures connect functions to the state they need without global variables or
extra parameters. Understanding capture modes prevents dangling-reference bugs in C++
and unexpected mutation in C# and Python. The next chapter addresses how programs
signal and recover from failures.
6.6 References
Rust Closures - The Book
C++ Lambda Expressions - cppreference
C# Lambda Expressions - Microsoft
Python Lambda Expressions