Closure Architecture (Fn, FnMut, FnOnce) & Zero-Cost Iterators

Master anonymous closure structs, environment capture modes, the closure trait hierarchy (Fn, FnMut, FnOnce), and lazy zero-cost iterators.

03. Advanced Abstractions, Traits & Smart Pointers7 min read

Functional programming in Rust is not an afterthought; it is built into the language core with zero runtime overhead.

Under the hood, closures in Rust are not opaque runtime function objects or dynamic lambda dictionaries. They are anonymous structs synthesized by the compiler with explicit field layouts matching their captured environment.

Trait HierarchySubtyping
Fn -> FnMut -> FnOnce

Every closure implementing Fn also implements FnMut, and every FnMut closure implements FnOnce.

1. How Closures are Represented in Hardware#

When you write a closure, rustc creates an unnameable anonymous struct containing fields for every variable captured from the surrounding lexical scope:

1
2
3
4
5
6
7
8
9
10
fn main() { let factor = 10; let offset = 5; // The compiler synthesizes an anonymous struct: // struct ClosureEnvironment<'a> { factor: &'a i32, offset: &'a i32 } let calculate = |x: i32| (x * factor) + offset; println!("Result: {}", calculate(3)); // 35 }

Depending on how captured variables are manipulated within the closure body, the fields of this anonymous struct will store values by reference (&T), by mutable reference (&mut T), or by value (ownership move).

2. The Three Closure Traits#

Rust distinguishes three closure traits based on the manner in which the closure consumes its captured environment:

TraitCalling SignatureCapture MechanismTimes CallableTypical Use Case
FnOncefn call_once(self, args) -> OutputBy value (consumes self / moves fields).Strictly once.Thread initialization, teardown callbacks.
FnMutfn call_mut(&mut self, args) -> OutputBy mutable borrow (&mut self).Multiple times.Accumulators, counters, event handlers.
Fnfn call(&self, args) -> OutputBy immutable borrow (&self).Multiple times (concurrently safe).Pure calculations, filter predicates.
Architecture Diagram
Rendering diagram...

Mathematically, the closure traits form a subset hierarchy:

{c∣c∈Fn}⊆{c∣c∈FnMut}⊆{c∣c∈FnOnce}\{c \mid c \in Fn\} \subseteq \{c \mid c \in FnMut\} \subseteq \{c \mid c \in FnOnce\}

If a function requests an argument implementing FnOnce, you can pass it an Fn closure, an FnMut closure, or an FnOnce closure.

The 'move' Keyword
Prefixing a closure with move (e.g. move || println!("{}", text)) forces the closure to take ownership of captured variables by value, even if the closure body only performs read-only operations. This is essential when spawning threads (std::thread::spawn), ensuring the captured data outlives the originating stack frame.

3. Zero-Cost Lazy Iterators#

Iterators in Rust are completely lazy: constructing an iterator chain allocates zero heap memory and executes zero loop iterations until a consumer method (like .collect(), .sum(), or .fold()) drives the pipeline:

1
2
3
4
5
6
7
8
fn compute_sum_of_squares(numbers: &[i32]) -> i32 { // Zero allocations: compiles into a single pointer-incrementing CPU loop! numbers .iter() .filter(|&&x| x % 2 == 0) .map(|&x| x * x) .sum() }

Because iterator adapters (Map, Filter, Zip) are generic structs evaluated at compile time, LLVM inlines the entire pipeline into the exact equivalent of a handcrafted C while loop, complete with loop unrolling and SIMD auto-vectorization.

4. Concept Verification#

Test your understanding of closures and iterator pipelines:

Concept check

What dictates whether a closure implements Fn, FnMut, or FnOnce in Rust?

Concept check

Why are Rust iterator chains considered 'zero-cost abstractions' compared to Python map/filter generators?