Closures

Rust is a systems programming language focused on speed, memory safety, and fearless concurrency — and closures are anonymous functions that can capture variables from the scope around them.

Learn Closures in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.

Part of the free Rust course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll write closures, capture the environment, pass and return them with the Fn traits, and use move .

What You'll Learn in This Lesson

1️⃣ Closure Basics and Capturing

A closure is written with its parameters between vertical bars, like |a, b| a + b . Types are usually inferred. The defining feature is that a closure captures variables from the surrounding scope — so it can use values that aren't passed in as parameters.

The scale closure used factor even though it was never a parameter — it captured it from the enclosing scope. A plain fn can't do that.

2️⃣ Passing, Returning, and the Fn Traits

Closures are values you can pass to and return from functions. A parameter typed impl Fn(i32) -> i32 accepts any read-only closure of that shape. To return a closure that outlives the function, use move so it owns its captures. Mutating closures use FnMut .

make_adder returned a closure that captured n by value (thanks to move ), so it stays valid after the function returns. The increment closure mutated count across calls — that's FnMut .

Your turn. Fill in the blanks marked ___ , then run it.

Write a function that returns a multiplying closure with move . Run it with cargo run .

📋 Quick Reference — Closures

Practice quiz

What defining ability does a closure have that a plain fn does not?

  • It can be recursive
  • It can capture variables from the surrounding scope
  • It runs faster
  • It returns multiple values

Answer: It can capture variables from the surrounding scope. Closures capture variables from their environment; plain functions only see parameters and globals.

How are a closure's parameters written?

  • In parentheses ( )
  • Between vertical bars | |

Answer: Between vertical bars | |. Closure parameters go between vertical bars, e.g. |a, b| a + b.

What does this print? let factor = 3; let scale = |x| x * factor; println!("{}", scale(5));

  • 3
  • 5
  • 15
  • 8

Answer: 15. The closure captures factor (3) and multiplies: 5 * 3 = 15.

Which trait does a closure implement if it only reads its captured values?

  • Fn
  • FnMut
  • FnOnce
  • Copy

Answer: Fn. A read-only closure implements Fn and can be called many times.

Which trait describes a closure that mutates a captured value?

  • Fn
  • FnMut
  • FnOnce
  • Drop

Answer: FnMut. FnMut closures mutate captured state and require mutable access to be called.

Which trait describes a closure that consumes (moves out) a captured value, callable at most once?

  • Fn
  • FnMut
  • FnOnce
  • Iterator

Answer: FnOnce. FnOnce closures take ownership of a capture, so they can be called only once.

What does this print? let mut count = 0; let mut inc = || { count += 1; count }; println!("{} {} {}", inc(), inc(), inc());

  • 0 1 2
  • 1 2 3
  • 3 3 3
  • 1 1 1

Answer: 1 2 3. The FnMut closure increments and returns count each call: 1, then 2, then 3.

What does the move keyword do to a closure's captures?

  • Borrows them
  • Forces the closure to take ownership of them
  • Copies them lazily
  • Deletes them

Answer: Forces the closure to take ownership of them. move makes the closure own its captures so it can outlive the scope it was created in.

What does this print? fn make_adder(n: i32) -> impl Fn(i32) -> i32 { move |x| x + n } let add_ten = make_adder(10); println!("{}", add_ten(7));

  • 10
  • 7
  • 17
  • 70

Answer: 17. make_adder(10) returns a closure adding 10, so add_ten(7) is 17.

When is move almost always required?

  • For one-line closures
  • When sending a closure to another thread
  • When the closure takes no parameters
  • When using type inference

Answer: When sending a closure to another thread. Closures sent to another thread must own their captures, so they need move.