Rust Closure Type Inference
Closure type inference is the compiler's process for finding a closure's types. Closures rarely write their types in the source.
No clues, no types
A closure that is stored and never used gives the compiler nothing to guess from. This whole program fails with error E0282, type annotations needed:
let identity = |x| x;
// never called, never annotated: x could be anythingAnnotations pin the types on purpose
Writing the types once ends all guessing. From then on, every call is checked against them:
let parse = |s: &str| -> i32 { s.parse().unwrap_or(0) };
println!("{} {}", parse("42"), parse("oops"));Identical text, different types
Each closure expression creates a brand-new type, even when the text matches word for word. Two capturing closures can therefore never be the two arms of one if:
let pick = if flag {
move |n: i32| n + a
} else {
move |n: i32| n + b
}; // error E0308: if and else have incompatible typesA near miss: plain closures in both arms do compile
Two closures that capture nothing escape the rule. Each one can be turned into the same fn pointer type. That shared type unifies the arms. It is the capturing that makes closure types unmatchable:
let pick = if flag { |n: i32| n + 1 } else { |n: i32| n + 1 };
println!("{}", pick(1));A closure cannot call itself
Inside the body, the closure's own name is not in scope yet. Recursion belongs to a named fn — or to a helper that receives the closure as an argument:
let fact = |n: u32| if n <= 1 { 1 } else { n * fact(n - 1) };
// error E0425: cannot find `fact` in this scopeOnly empty-handed closures become fn pointers
Writing fn(i32) -> i32 as a closure's type forces a conversion that only non-capturing closures can make. Add one capture and the same line fails:
let f: fn(i32) -> i32 = |x| x + 1; // fine: captures nothing
let g: fn(i32) -> i32 = |x| x + base; // error E0308: expected fn pointer, found closureExample
fn apply<F>(f: F, x: i32) -> i32
where
F: Fn(i32) -> i32,
{
f(x)
}
fn factorial(n: u32) -> u32 {
if n <= 1 {
1
} else {
n * factorial(n - 1)
}
}
fn main() {
// No annotation needed here: the Fn bound pins the types.
println!("doubled = {}", apply(|v| v * 2, 21));
// An annotated closure: types written once, checked at every call.
let shout = |word: &str| -> String { word.to_uppercase() };
println!("{}", shout("rust"));
// Closures cannot call themselves; a plain fn does the recursing.
println!("5! = {}", factorial(5));
}Key points
- Inference flows in both directions.
- A bound where the closure is used can pin its types.
- One example is Fn(i32) -> i32 on a parameter.
- The types are pinned before the body is even read.
- Types are pinned once per closure.
- The first pinning may come from a call, an annotation or a bound.
- After that, every other use must match.
- A closure type is unique and anonymous.
- You can store the value.
- You can never write its type name.
- impl Fn and Box<dyn Fn> exist for exactly that reason.
- Both come from the Returning Closures lesson.
- An fn writes every type in its signature, which is why closure errors sometimes name types you never wrote anywhere.
- Closure type inference finds a closure's parameter and return types without written annotations.
In this example, three closures get their types three different ways. apply demands Fn(i32) -> i32. So the closure |v| v * 2 handed to it needs no annotation. The bound pins v to i32. The call prints doubled = 42. shout writes its types out, |word: &str| -> String, and prints RUST. The last part is the escape hatch for the no-self-call rule. factorial is an ordinary named fn. It is free to call itself. It prints 5! = 120.
Note
None of this applies to named functions.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Why do two capturing closures with identical bodies fail as the two arms of an if/else?