Lesson 17 of 20

Closures

Closures

Closures are anonymous functions that can capture values from their surrounding scope.

Syntax

let add = |x, y| x + y;          // type inferred
let square = |x: i32| x * x;     // explicit type
let always_one = || 1;            // no parameters

Capture Semantics

Rust closures capture variables from their environment in three ways, and the compiler picks the least restrictive mode that works:

1. Borrow immutably (Fn) — the closure only reads the captured value:

let name = String::from("Alice");
let greet = || println!("Hello, {}", name); // borrows &name
greet();
greet(); // can call multiple times
println!("{}", name); // name still usable

2. Borrow mutably (FnMut) — the closure modifies the captured value:

let mut count = 0;
let mut inc = || { count += 1; count };
inc(); // count is now 1
inc(); // count is now 2

3. Move / consume (FnOnce) — the closure takes ownership:

let name = String::from("Alice");
let consume = || { drop(name); }; // moves name into closure
consume(); // name is dropped
// consume(); ← ERROR: cannot call FnOnce twice

The move Keyword

move forces the closure to take ownership of all captured variables, even if it only reads them. This is essential when the closure outlives the scope it was created in:

fn make_greeter(name: String) -> impl Fn() -> String {
    move || format!("Hello, {}!", name) // must move: name would be dropped otherwise
}

Closure Trait Hierarchy

Every closure implements FnOnce. If it doesn't consume captures, it also implements FnMut. If it doesn't mutate captures, it also implements Fn:

Fn ⊂ FnMut ⊂ FnOnce

A function accepting impl FnOnce can take any closure. A function requiring impl Fn only accepts closures that borrow immutably.

Higher-Order Functions

Functions that accept closures use trait bounds:

fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
    f(x)
}
apply(|x| x * 2, 5); // 10

Returning Closures

fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
    move |x| x + n
}
let add5 = make_adder(5);
add5(3); // 8

Your Task

  1. apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 — applies f to x.
  2. apply_n_times<F: Fn(i32) -> i32>(f: F, x: i32, n: u32) -> i32 — applies f to x n times.
  3. make_adder(n: i32) -> impl Fn(i32) -> i32 — returns a closure that adds n.
  4. make_multiplier(n: i32) -> impl Fn(i32) -> i32 — returns a closure that multiplies by n.
  5. make_counter() -> impl FnMut() -> i32 — returns a closure that returns an incrementing count (1, 2, 3, ...) each time it is called. This exercises FnMut capture semantics.
  6. consume_and_length(s: String) -> impl FnOnce() -> usize — returns a closure that moves s into itself and returns its length. This exercises FnOnce / move capture semantics.
Rust (Miri) loading...
Loading...
Click "Run" to execute your code.