Rust Closures: Fn, FnMut, FnOnce, and move Explained

Introduction

Closures are anonymous functions that can capture values from their surrounding scope. You’ve used them in iterator chains like .map() and .filter() — this post explains how they work under the hood, what the three closure traits mean, and how to write functions that accept or return closures.

Closure Syntax

Closures use |params| body — vertical bars instead of parentheses:

fn main() {
    let add = |x, y| x + y;
    println!("{}", add(2, 3)); // 5

    let square = |x: i32| -> i32 {
        x * x
    };
    println!("{}", square(4)); // 16
}

Types are inferred from usage. Explicit annotations are optional.

Capturing the Environment

Unlike regular functions, closures can read variables from the enclosing scope:

fn main() {
    let threshold = 10;
    let is_big = |n| n > threshold; // borrows threshold

    println!("{}", is_big(15)); // true
    println!("{}", is_big(5));  // false
}

The closure borrows threshold — no copy needed.

The Three Closure Traits

Every closure implements one or more of these traits, from most to least restrictive:

Trait Callable What it does with captures
FnOnce Once only Moves (consumes) captured values
FnMut Multiple times Mutably borrows captures
Fn Multiple times Immutably borrows captures

Every closure is at least FnOnce. If it doesn’t consume captures, it’s also FnMut. If it doesn’t mutate them, it’s also Fn.

FnOnce — Consuming Closures

fn run_once(f: impl FnOnce()) {
    f();
    // f(); // error: value used after move
}

fn main() {
    let name = String::from("Alice");
    let greet = move || println!("Hello, {}!", name); // moves name in
    run_once(greet);
}

move forces ownership transfer into the closure. After run_once, both the closure and name are gone.

FnMut — Mutating Closures

fn apply_twice(mut f: impl FnMut()) {
    f();
    f();
}

fn main() {
    let mut count = 0;
    let bump = || {
        count += 1;
        println!("count = {}", count);
    };
    apply_twice(bump);
    // count = 1
    // count = 2
}

Fn — Shared Closures

Most closures you write are Fn — they just read captured values:

fn apply_to_5(f: impl Fn(i32) -> i32) -> i32 {
    f(5)
}

fn main() {
    let offset = 10;
    let result = apply_to_5(|x| x + offset);
    println!("{}", result); // 15
    // offset is still usable here
}

Closures in Iterator Chains

The most common place you’ll encounter closures:

fn main() {
    let numbers = vec![1, 2, 3, 4, 5, 6];

    let result: Vec<i32> = numbers
        .iter()
        .filter(|&&x| x % 2 == 0)
        .map(|&x| x * 2)
        .collect();

    println!("{:?}", result); // [4, 8, 12]
}

Returning Closures

Use impl Fn when there’s a single concrete closure type:

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

fn main() {
    let add5 = make_adder(5);
    println!("{}", add5(3));  // 8
    println!("{}", add5(10)); // 15
}

Need to return different closure types at runtime? Use a trait object:

fn make_op(double: bool) -> Box<dyn Fn(i32) -> i32> {
    if double {
        Box::new(|x| x * 2)
    } else {
        Box::new(|x| x + 1)
    }
}

Storing Closures in Structs

struct Callback {
    action: Box<dyn Fn()>,
}

impl Callback {
    fn new(f: impl Fn() + 'static) -> Self {
        Callback { action: Box::new(f) }
    }

    fn run(&self) {
        (self.action)();
    }
}

fn main() {
    let cb = Callback::new(|| println!("Triggered!"));
    cb.run(); // Triggered!
    cb.run(); // Triggered!
}

Exercises

  1. Write apply_n(f: impl FnMut(), n: usize) that calls f exactly n times.
  2. Write make_multiplier(factor: i32) -> impl Fn(i32) -> i32 — a factory that returns a closure multiplying its input by factor.
  3. Using only iterator methods (no explicit loops), take a Vec<String>, keep strings longer than 4 chars, and collect their uppercase versions into a new Vec<String>.

Summary

  • Closures capture environment; regular functions don’t
  • move transfers ownership of captured variables into the closure
  • Prefer impl Fn in function signatures; use FnMut or FnOnce only when needed
  • Return with impl Fn(…) for single types; Box<dyn Fn(…)> for dynamic dispatch

Leave a Comment

Your email address will not be published. Required fields are marked *