Skip to main content
A closure in Rust is an anonymous function capable of capturing variables from its enclosing lexical scope. Under the hood, the Rust compiler implements closures as anonymous, compiler-generated structs that store the captured environment, combined with an implementation of one or more of the callable traits (Fn, FnMut, or FnOnce).

Syntax and Type Inference

Closures are defined using pipe characters (|) to enclose parameters, followed by the closure body. Unlike standard functions (fn), closures do not strictly require explicit type annotations for parameters or return values.
The compiler infers the types based on the closure’s first usage. Once inferred, the types are strictly locked. Invoking the same closure later with a different type results in a compilation error. Every closure instance has a unique, unnamable type, even if two closures have identical signatures and bodies.

Environment Capturing Mechanics

Rust automatically infers how a closure captures its environment based on the operations performed inside the closure body. The compiler defaults to the least restrictive capture method possible:
  1. Immutable Borrow (&T): The closure only reads the captured variable.
  2. Mutable Borrow (&mut T): The closure modifies the captured variable.
  3. Ownership (T): The closure consumes the variable (e.g., moving it into a new allocation or dropping it).
You can explicitly force a closure to take ownership of its captured environment by prepending the move keyword. This is strictly a memory-transfer operation and does not dictate which callable trait the closure implements.

The Closure Traits

Because every closure has a unique type, passing closures as arguments or returning them requires generics bounded by one of three standard library traits. The compiler automatically implements these traits based on how the closure uses (reads, mutates, or consumes) the captured variables inside its body, regardless of how they were initially captured:
  • FnOnce: The closure can be called at least once. It is implemented by all closures. If a closure moves captured variables out of its environment (e.g., by dropping them or returning them), it only implements FnOnce and is consumed upon invocation.
  • FnMut: The closure can be called multiple times and is permitted to mutate its captured environment. Because Fn is a subtrait of FnMut, closures that only read their environment also implement FnMut.
  • Fn: The closure can be called multiple times without mutating its environment. It only requires shared, immutable access to its internal state.

Compiler Desugaring

To understand closure memory layout, it is helpful to view how the compiler desugars them. When a closure captures variables, Rust generates a struct to hold those variables.
Conceptually, the compiler translates the above closure into a struct and implements a callable interface. Because manual implementation of the Fn traits is unstable in Rust, the following valid Rust code uses a custom ConceptualFnOnce trait to accurately demonstrate the compiler’s internal mechanics:
Tired of Poor Rust Skills? Fix That With Deep Grasping!Learn More