Closures

Closure Type Inference

Unlike named functions (fn), which require explicit parameter and return type annotations because they form part of a module or public API boundary, closures infer their parameter and return types from their body and how they are first used within a function.

How the Compiler Infers Closure Types

The compiler determines a closure's signature from two sources:

  1. Target context: Passing a closure to a function or iterator adapter with a known signature (such as Iterator::map or Option::unwrap_or_else) immediately constrains the parameter and return types.
  2. First call site: If a closure is bound to a local variable without annotations, the first call to that closure locks in its concrete parameter and return types.
fn main() {
    // 1. Inferred from iterator context (x is inferred as &i32):
    let doubled: Vec<i32> = [1, 2, 3].iter().map(|x| x * 2).collect();

    // 2. Inferred from the first call site (s is locked to &str):
    let identity = |s| s;
    let word = identity("infer");

    println!("Doubled: {doubled:?}, word: {word}");
}
Doubled: [2, 4, 6], word: infer

Type Lock-In After First Use (E0308)

An unannotated closure is not polymorphic across multiple types. Once the compiler infers a concrete type for a closure's parameter from its first use, calling the same closure with a different type fails with E0308:

// ❌ does not compile
fn main() {
    let echo = |x| x;

    let s = echo(String::from("text")); // Locks `x` to `String`
    let n = echo(42);                   // Error: expected `String`, found integer
}
error[E0308]: mismatched types
 --> src/main.rs:6:18
  |
6 |     let n = echo(42);
  |             ---- ^^ expected `String`, found integer
  |             |
  |             arguments to this function are incorrect
  |
note: closure parameter defined here
 --> src/main.rs:3:17
  |
3 |     let echo = |x| x;
  |                 ^

Unused Unannotated Closures (E0282)

If a closure is declared without type annotations and is never called or passed to a typed consumer, the compiler cannot infer its parameter types and emits E0282:

// ❌ does not compile
fn main() {
    let uncalled = |x| x;
}
error[E0282]: type annotations needed
 --> src/main.rs:3:21
  |
3 |     let uncalled = |x| x;
  |                     ^
  |
help: consider giving this closure parameter an explicit type
  |
3 |     let uncalled = |x: /* Type */| x;
  |                      ++++++++++++

Unique Anonymous Compiler Types

Every closure expression in source code has its own unique, unnameable compiler-generated type, even if two closures have the exact same parameter types, return type, and body. Consequently, you cannot assign one closure variable to another without boxing (Box<dyn Fn(...)>) or coercing non-capturing closures to function pointers (fn(...)).