Web Analytics

Closures - Anonymous Functions

Advanced ~35 min read

Closures are anonymous functions that can capture variables from their surrounding environment. They're incredibly useful for functional programming patterns, working with iterators, and writing concise, expressive code. Rust's closures are zero-cost abstractions with powerful type inference.

What Are Closures?

A closure is an anonymous function you can save in a variable or pass as an argument to other functions. Unlike regular functions, closures can capture values from the scope in which they're defined.

Key Difference: Functions use fn keyword and can't capture environment. Closures use || syntax and can capture variables from their surrounding scope.

Closure Syntax

Rust Closures
Output
Click Run to execute your code

Syntax Variations

Syntax Example Use Case
Simple |x| x + 1 Single expression, type inference
With types |x: i32| -> i32 { x + 1 } Explicit types
Multiple params |x, y| x + y Multiple parameters
Block body |x| { let y = x * 2; y + 1 } Multiple statements

Capturing the Environment

Closures can capture variables from their surrounding scope in three ways:

Output
Click Run to execute your code

The Three Fn Traits

Trait Capturing Can Call Example
Fn Borrows immutably Multiple times || println!("{}", x)
FnMut Borrows mutably Multiple times || list.push(4)
FnOnce Takes ownership Once || drop(x)
Pro Tip: Rust automatically chooses the most restrictive trait that works. If a closure only reads variables, it implements Fn. If it modifies them, FnMut. If it consumes them, FnOnce.

The move Keyword

Use move to force a closure to take ownership of captured variables:

let x = vec![1, 2, 3];
let closure = move || println!("{:?}", x);

closure();
// println!("{:?}", x);  // Error: x was moved
When to use move: Essential for threads and async code where the closure needs to outlive the current scope.

Closures with Iterators

Output
Click Run to execute your code

Common Iterator Methods

Method Purpose Example
map Transform each element .map(|x| x * 2)
filter Keep matching elements .filter(|x| *x > 5)
fold Reduce to single value .fold(0, |acc, x| acc + x)
find Find first match .find(|x| *x == 5)
any Check if any match .any(|x| *x > 10)
all Check if all match .all(|x| *x > 0)

Returning Closures

You can return closures using impl Fn:

fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

let add_5 = make_adder(5);
println!("{}", add_5(10));  // 15

Type Inference

Closures have powerful type inference:

// Compiler infers types from usage
let add = |x, y| x + y;
let result = add(5, 10);  // Infers i32

// Once used, types are fixed
// let result2 = add(5.0, 10.0);  // Error: expected i32

Best Practices

  • Use closures for short functions: Especially with iterators
  • Let type inference work: Only add types when needed
  • Use move for threads: Ensure closure owns its data
  • Chain iterator methods: Readable, efficient functional code
  • Prefer closures over loops: More expressive with iterators
  • Know your Fn traits: Understand capturing behavior

Common Mistakes

1. Forgetting to dereference in closures

// Wrong - comparing references
let numbers = vec![1, 2, 3];
let evens: Vec<&i32> = numbers.iter()
    .filter(|x| x % 2 == 0)  // Error: can't mod &i32
    .collect();

// Correct - dereference with *
let evens: Vec<&i32> = numbers.iter()
    .filter(|x| *x % 2 == 0)
    .collect();

2. Using closure after move

// Wrong - using moved value
let x = vec![1, 2, 3];
let closure = move || println!("{:?}", x);
closure();
println!("{:?}", x);  // Error: x was moved

// Correct - clone if you need both
let x = vec![1, 2, 3];
let x_clone = x.clone();
let closure = move || println!("{:?}", x_clone);
closure();
println!("{:?}", x);  // OK!

3. Trying to return closure without impl Fn

// Wrong - can't return closure type directly
fn make_adder(x: i32) -> ??? {  // What type?
    |y| x + y
}

// Correct - use impl Fn
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

Exercise: Closures Practice

Task: Practice closures with temperature conversion, filtering, and functional patterns.

Output
Click Run to execute your code
Show Solution
let square = |x| x * x;

let base = 10;
let add_base = |x| x + base;

let fahrenheit: Vec = celsius.iter()
    .map(|c| c * 9 / 5 + 32)
    .collect();

let result: Vec = numbers.iter()
    .filter(|x| *x > 5)
    .map(|x| x * x)
    .collect();

let sum: i32 = nums.iter()
    .filter(|x| *x % 2 == 0)
    .sum();

fn make_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
    move |x| x * factor
}

let max = values.iter()
    .fold(0, |acc, x| if x > &acc { *x } else { acc });

Summary

  • Closures are anonymous functions that capture environment
  • Syntax: |params| expression or |params| { body }
  • Three Fn traits: Fn, FnMut, FnOnce
  • Fn borrows immutably, can call multiple times
  • FnMut borrows mutably, can call multiple times
  • FnOnce takes ownership, can call once
  • move keyword forces ownership transfer
  • Work seamlessly with iterators
  • Type inference makes closures concise
  • Zero-cost abstraction - no runtime overhead

What's Next?

Now that you understand closures, you're ready to explore Iterators in depth - how to create custom iterators, use iterator adapters, and write efficient, functional code. Closures and iterators work together to enable powerful functional programming patterns in Rust.