Advanced Type Features

Closure Types

Every closure expression (|args| body) in Rust causes the compiler to synthesize a unique, anonymous struct type that stores the closure's captured variables as fields and implements one or more of the closure traits (FnOnce, FnMut, Fn). Even two identical closures written on adjacent lines have distinct anonymous types (E0308).

How the Compiler Desugars a Closure into a Struct

Source Closure:
  let factor: u64 = 10;
  let mut calls: u32 = 0;
  let mut scale = |x: u64| { calls += 1; x * factor };

Compiler-Synthesized Anonymous Struct (16 bytes on 64-bit targets):
  struct __ClosureEnv<'a, 'b> {
      factor_ref: &'a u64,       // Shared borrow (8 bytes)
      calls_ref:  &'b mut u32,   // Mutable borrow (8 bytes)
  }
  impl FnOnce<(u64,)> for __ClosureEnv<'_, '_> { ... }
  impl FnMut<(u64,)>  for __ClosureEnv<'_, '_> { ... }

Because each closure type is anonymous and cannot be written directly in source code, functions accept or return closures using:

  • Static dispatch: impl Fn(u64) -> u64 or generic <F: Fn(u64) -> u64>.
  • Dynamic dispatch: &dyn Fn(u64) -> u64 or Box<dyn Fn(u64) -> u64>.
  • Function pointer coercion (only when the closure captures zero variables): fn(u64) -> u64.

Memory Size and Capture Modes of Closure Types

A closure's byte size (size_of_val(&closure)) equals the sum of the fields it captures (plus alignment padding):

Closure Capture Mode What the Anonymous Closure Struct Stores Size (size_of_val) Trait Hierarchy Implemented
Non-capturing (|x| x + 1) Zero fields (ZST, coerces to fn pointer) 0 bytes Fn + FnMut + FnOnce + Copy + Clone
By shared reference (|x| x + factor) &factor per read-only captured variable 8 bytes per reference Fn + FnMut + FnOnce + Copy + Clone
By mutable reference (|x| { count += 1; x }) &mut count per mutated variable 8 bytes per reference FnMut + FnOnce (not Fn, not Copy)
By value (move or consumed) The owned values (String, Vec, u64) directly inline Sum of moved values Fn / FnMut / FnOnce depending on whether body moves out of captures
fn main() {
    let stateless = |x: i32| x + 1;

    let multiplier: u64 = 5;
    let borrows_imm = |x: u64| x * multiplier;

    let owned_prefix = String::from("tag:");
    let moves_string = move |suffix: &str| format!("{}{}", owned_prefix, suffix);

    println!("size of stateless closure:   {} bytes", size_of_val(&stateless));
    println!("size of borrows_imm closure: {} bytes", size_of_val(&borrows_imm));
    println!("size of moves_string closure: {} bytes", size_of_val(&moves_string));

    // A closure that only captures shared references or `Copy` values is itself `Copy`!
    let copy_of_borrows = borrows_imm;
    println!("borrows_imm(10) = {}, copy(10) = {}", borrows_imm(10), copy_of_borrows(10));
    println!("moves_string: {}", moves_string("ok"));
}
size of stateless closure:   0 bytes
size of borrows_imm closure: 8 bytes
size of moves_string closure: 24 bytes
borrows_imm(10) = 50, copy(10) = 50
moves_string: tag:ok

Auto-Trait Propagation on Closure Types (Copy, Clone, Send, Sync)

The compiler automatically implements Copy, Clone, Send, and Sync for a closure's anonymous struct based on its captured fields:

  • If all captured values (or captured &T references) implement Copy (or Clone), the closure itself implements Copy (or Clone)! (Note that &mut T is not Copy or Clone, so a closure capturing &mut T is neither Copy nor Clone.)
  • If all captured values/references are Send (and Sync), the closure itself implements Send (and Sync).