A function with no name
You have written this in every language you know. You needed a small piece of behaviour right where you stood, a comparison, a transform, a callback, and you did not want to climb out to the top of the file to declare a named function for it. So you wrote a function literal inline: func(x int) int { return x * 2 } in Go, x => x * 2 in JavaScript, lambda x: x * 2 in Python. Rust has the same thing, and it calls it a closure: an anonymous function you write inline, with a light syntax of its own. Run this:
// A closure is an anonymous function written inline, with |params| body.
fn main() {
// No name, no fn keyword: just the parameter list between bars and a body.
let double = |x| x * 2;
// You call it exactly like a named function.
println!("double(21) = {}", double(21));
// The body can be a block when it needs more than one expression.
let label = |n: i32| {
let kind = if n % 2 == 0 { "even" } else { "odd" };
format!("{n} is {kind}")
};
println!("{}", label(7));
}The syntax is the parameter list between two vertical bars, then the body. |x| x * 2 reads as a function of one parameter x whose body is x * 2. You bind it to a name with let like any other value, and you call it with the same name(args) form you use for a real function. When the body is a single expression you write it bare; when it needs more than one statement you wrap it in a block with braces, exactly as the label closure does.
Notice the types. double wrote no type for x and no return type, and it compiled anyway. A closure is almost always used right where it is defined, so the compiler can look at the call site, see that you passed an i32, and infer the parameter and return types for you. The label closure annotates n: i32 only because it is useful documentation, not because the compiler demands it. This is the first reason closures feel lighter than named functions: a named fn must spell out every type in its signature, while a closure may lean on inference.
Write |params| bodyand you have a function with no name that you can store in a variable, pass to another function, or return. The parameter and return types are usually inferred from how you use it, so the form stays short. So far this is just Go’s func literal with tighter syntax. The next section is where closures become more than that.
Remembering the scope around them
A plain function can only see its own parameters and whatever is global. It has no memory of the place it was written. A closure does. The word closureis short for “closes over its environment”: the closure can reach out and use the variables in the scope where it was defined, without you passing them in as arguments. That captured context is what the closure remembers. Run this:
// The thing that makes a closure more than a function: it can see the
// variables around it, without you passing them in as arguments.
fn main() {
let tax_rate = 0.2_f64; // a free variable, defined outside the closure
// with_tax never takes tax_rate as a parameter; it reads it from the
// surrounding scope. That captured value is "remembered" by the closure.
let with_tax = |price: f64| price * (1.0 + tax_rate);
println!("12.00 with tax = {:.2}", with_tax(12.00));
println!("40.00 with tax = {:.2}", with_tax(40.00));
// A plain fn cannot do this: a fn has no surrounding scope to remember.
}Look at with_tax. Its only parameter is price, yet the body uses tax_rate, a variable defined a line earlier and never passed in. The closure captured tax_rate from the surrounding scope, so each call multiplies by a rate the closure carries with it. A named fn declared at this spot could not do that: a fn has no surrounding scope to remember, so it would have to take tax_rate as a second parameter and make every caller supply it.
If you write Go, you have leaned on exactly this. A handler that closes over a database pool, a comparison that closes over a sort key, a goroutine that closes over a channel: all of those are closures capturing their environment. Rust gives you the same power. What it adds, and the rest of this chapter is about, is being precise about how a captured value is held: borrowed, mutably borrowed, or owned outright. In Go the runtime quietly moves a captured variable to the heap and shares it. In Rust the capture mode is part of the program, decided by the compiler and visible in the rules you are about to meet.
Other languages call these lambdas or anonymous functions, and Rust closures are all of those things. The name closure emphasises the one capability a bare function lacks: it closes over the variables around it, sealing them in so they travel with the function value. The scary word is just pointing at the feature from the last paragraph. A closure is a function plus the piece of scope it remembered.
The three capture modes, chosen for you
Here is the question that does not even exist in Go: when a closure captures a variable, does it borrow it, mutably borrow it, or take ownership of it? You already know these three relationships from the ownership chapters (III.02 and III.03): a shared reference &, an exclusive reference &mut, and a move that transfers ownership. A closure capture is one of those same three, and Rust picks the least invasive one its body can get away with. You do not declare the mode; the body decides it. Run this and read each block against the comment:
// What the body does decides how each variable is captured. Rust picks the
// least invasive mode that still lets the body work.
fn main() {
// 1. Read only -> captured by shared reference (&). greeting is still
// usable afterwards, because the closure only borrowed it.
let greeting = String::from("hello");
let say = || println!("borrowed: {greeting}");
say();
println!("still mine: {greeting}");
// 2. Mutate -> captured by exclusive reference (&mut). count must be mut,
// and the closure itself must be mut to be called.
let mut count = 0;
let mut bump = || {
count += 1;
println!("count is now {count}");
};
bump();
bump();
// 3. Consume -> captured by value. push_str needs to own the buffer to
// hand it off, so the closure takes ownership of buffer.
let buffer = String::from("log: ");
let finish = move || {
let mut owned = buffer;
owned.push_str("done");
owned
};
println!("{}", finish());
}Three closures, three modes, all chosen automatically. The first, say, only reads greeting, so the compiler captures it by shared reference, a borrow. Because it is only borrowed, greeting is still yours after the closure is defined and called, which is why the println! below it still works. The second, bump, mutates count, so the compiler captures it by exclusive reference, a &mut. That is also why count is declared mut and why bump itself must be mut to be called: a closure that holds a &mut changes state when it runs, so calling it is a mutation.
The third, finish, consumes buffer: it moves the string into a local, appends to it, and hands it back. You cannot consume something you only borrowed, so the compiler captures buffer by value, taking ownership of it. After finish is defined, buffer belongs to the closure and you cannot use the original name again. The rule across all three is one sentence: the closure captures each variable in the gentlest way that still lets the body do what it does, read borrows, mutate borrows mutably, consume takes ownership.
You never write &, &mut, or a move on a capture. The compiler reads the closure body, sees whether each variable is read, mutated, or consumed, and captures it by shared reference, exclusive reference, or value to match. These are the exact same three relationships from III.02 and III.03; closures simply apply them automatically instead of making you spell them out.
The move keyword: forcing capture by value
The automatic choice from the last section is almost always what you want, with one important exception. Sometimes a closure needs to outlive the scope that created its captured variables. The clearest case is a new thread: you spawn it, and it may keep running after the function that launched it has returned and its local variables are gone. If the closure had merely borrowed those locals, the borrow would point at memory that no longer exists, a dangling reference, and Rust’s whole point is to make that impossible. So the compiler refuses to let a borrowing closure escape that way, and you reach for move. Run this:
// The move keyword forces every capture to be by value, even reads that would
// otherwise only borrow. You reach for it when the closure must outlive the
// scope that created the values, the classic case being a new thread.
use std::thread;
fn main() {
let name = String::from("worker-1");
// Without move, this closure would borrow name. But the thread may run
// after main's scope ends, so a borrow would dangle. move makes the
// closure own name, so it stays alive as long as the closure does.
let handle = thread::spawn(move || {
println!("{name} is running");
});
handle.join().unwrap();
// name is gone here: the closure took ownership of it.
}The keyword goes right before the bars: move || { ... }. It tells the compiler to capture every variable the body uses by value, moving ownership into the closure, even ones the body only reads and would otherwise just borrow. Here name is read, not mutated, so without move it would be captured by a shared reference. But the thread might still be running after main’s scope ends, so a borrow is unsafe. move hands ownership of name to the closure, the closure carries the string with it onto the new thread, and the value lives exactly as long as the closure that owns it. After the spawn, name is no longer usable in main: it moved.
This is the link back to ownership made concrete. A move closure is just a closure whose captures are all moves, the same transfer of ownership you met in III.02 when you passed a String into a function by value. The closure becomes the new owner. You will use move any time the closure must live independently of where it was born: spawning a thread, returning a closure from a function (the next section), or storing a closure somewhere that outlives the current scope.
The keyword reads like it means “move this to another thread”, and threads are the common reason to use it, but that is not what it does. move means one thing only: capture every variable by value instead of by reference. The thread does not care; it just happens to be the situation that most often requires by-value capture. You can write a move closure and call it on the very next line, no threads in sight.
Returning a closure that remembers an argument
Closures earn their keep when a function builds one and hands it back, pre-loaded with some value. This is the classic factory: you call make_adder(10) and get back a brand new function that adds ten to whatever you pass it. The ten is baked in, remembered by the closure, long after make_adder has returned. Run this:
// Returning a closure: a function that builds and hands back a closure which
// remembers an argument. impl Fn(i32) -> i32 says "some closure of this shape".
fn make_adder(n: i32) -> impl Fn(i32) -> i32 {
// The returned closure captures n by value (move), so n lives on inside it
// after make_adder has returned and its stack frame is gone.
move |x| x + n
}
fn main() {
let add_ten = make_adder(10);
let add_one = make_adder(1);
// Each closure remembers its own captured n.
println!("add_ten(5) = {}", add_ten(5));
println!("add_one(5) = {}", add_one(5));
println!("add_ten(99) = {}", add_ten(99));
}Two things make this work. First, the return type is impl Fn(i32) -> i32, which reads as “some type that implements the Fn(i32) -> i32trait”, that is, some closure that takes an i32 and returns an i32. You cannot name a closure’s type directly, because every closure has its own anonymous compiler-generated type, so impl Fn(...)is how you promise “a closure of this shape” without naming it. Second, the body is move |x| x + n. The move is essential here: n is a local of make_adder, and its stack frame disappears the moment the function returns, so the closure must own n, not borrow it, or it would point at a dead frame. move gives the closure its own copy of n to carry away.
Because each call to make_adder captures its own n, add_ten and add_one are genuinely independent functions with different remembered values. This is the same idea you have written in Go when a function returns a func(int) int closing over a counter or a config value; Rust only asks you to be explicit that the closure owns what it took.
Each closure has a unique, unnameable type the compiler invents, so you cannot write -> SomeClosureType by hand. Instead you return impl Fn(Args) -> Ret, which says “I am returning some value that can be called with these arguments and returns this type”. Pair it with movewhenever the closure captures locals, so it owns them and survives the function’s return.
Taking a closure as a parameter
The other half of the story is the function that accepts a closure, so the caller can hand in behaviour. You have done this forever: a sort that takes a comparison, a filter that takes a predicate, a map that takes a transform. In Rust you write the parameter as a generic type bound by one of the closure traits. Here retainkeeps only the items for which the caller’s closure returns true. Run it:
// Taking a closure as a parameter: a generic with an Fn bound. retain keeps
// only the items for which keep returns true. keep can be any closure (or fn)
// of shape Fn(&i32) -> bool, so the caller decides the rule.
fn retain<F: Fn(&i32) -> bool>(items: Vec<i32>, keep: F) -> Vec<i32> {
let mut out = Vec::new();
for item in items {
if keep(&item) {
out.push(item);
}
}
out
}
fn main() {
let numbers = vec![3, 8, 1, 12, 7, 20];
// A captured threshold: the closure remembers min and uses it on each item.
let min = 7;
let big = retain(numbers.clone(), |&n| n >= min);
println!("at least {min}: {big:?}");
// The same retain accepts a different rule with no change to retain itself.
let even = retain(numbers, |&n| n % 2 == 0);
println!("even: {even:?}");
}Read the signature: retain<F: Fn(&i32) -> bool>. That introduces a generic type F with the bound Fn(&i32) -> bool, which means “F is anything you can call with a &i32 and get back a bool”: a closure of that shape, or even a plain function. The caller decides the rule. The first call passes |&n| n >= min, a closure that captures min from the surrounding scope, so the threshold rides along inside it. The second call passes a different closure entirely, and retain itself does not change a line. The behaviour is a parameter.
Writing the parameter as a generic F with an Fn bound is the default and the fastest: the compiler generates a specialised copy of retain for each closure type you pass, and the call is a direct, inlined call with no indirection. There is a second way to take a closure, Box<dyn Fn(&i32) -> bool>, which stores the closure behind a pointer as a trait object (the trait objects from III.13). You reach for that when you need to store many closures of different concrete types in one collection, or pick one at runtime, trading a little indirection for that flexibility. For a single parameter like this one, the generic bound is the idiomatic choice.
The bound Fn(&i32) -> bool looks like a function type, but it is a trait with special call-style syntax. It is sugar for Fn<(&i32,), Output = bool>, which you will almost never write. The takeaway: Fn, FnMut, and FnOnceare ordinary traits (III.10), so “a closure” is really “a value implementing one of these traits”, and the closure traits are how the type system talks about callable things.
Fn, FnMut, FnOnce: the three callable traits
You have now seen all three closure traits in passing, so here they are laid out. A closure implements one or more of Fn, FnMut, and FnOnce, and which ones it implements is decided by what it does to its captures, the same distinction as the capture modes, now seen from the trait side. The naming says what each one permits about calling the closure:
FnOnce: callable at least once. It may consume its captures (move them out), so calling it can use them up. A closure that moves a captured value out isFnOnceand onlyFnOnce: you can call it once, then it is spent.FnMut: callable many times, and may mutate its captures. It holds a&mutto what it captured, so each call can change that state, which is why you must hold it in amutbinding to call it.Fn: callable many times, read only. It only borrows its captures by shared reference (or captures nothing), so it never changes anything and can be called freely, even from several places at once.
Run this, where one function asks for FnMut because it calls its closure twice and lets it mutate, and another asks for FnOnce because it calls its closure exactly once:
// FnMut and FnOnce: a closure that mutates needs FnMut, and one that consumes a
// captured value can be called only once, so it is FnOnce.
// run_twice needs to call its closure two times while letting it mutate state,
// so it asks for FnMut and takes the closure by mut.
fn run_twice<F: FnMut()>(mut action: F) {
action();
action();
}
// consume calls its closure exactly once, so FnOnce is the loosest bound that
// works, and it accepts the widest set of closures (including consuming ones).
fn consume<F: FnOnce() -> String>(action: F) -> String {
action()
}
fn main() {
let mut total = 0;
run_twice(|| {
total += 10; // mutating a captured variable: this is FnMut
println!("total is {total}");
});
let banner = String::from("shipped");
// This closure moves banner out, so it can only run once: FnOnce.
let report = consume(move || banner);
println!("report: {report}");
}The three traits nest in a subset relationship, and this is the one piece worth memorising. Every Fn closure is also a FnMut (if you may read it freely, you may certainly call it where mutation is allowed), and every FnMut is also a FnOnce (if you may call it many times, you may certainly call it once). So the hierarchy is Fn: FnMut: FnOnce, each a tighter promise than the last. That tells you how to write a parameter bound: ask for the loosest trait your function actually needs. If you call the closure once, bound it by FnOnce and you will accept the widest set of closures, including consuming ones. If you call it repeatedly, you need FnMut or Fn. Fn is the strictest requirement, so it accepts the fewest closures, only the read-only ones.
FnOnce means “at least once, may consume”, FnMut means “repeatedly, may mutate”, Fn means “repeatedly, read only”. They nest: Fn is a FnMut is a FnOnce. When you take a closure as a parameter, bound it by the loosest trait your body needs (the fewest demands), and you will accept the most callers. When you call it once, FnOnce is the kindest bound you can offer.
Closures versus plain fn pointers
Not every callable needs to be a closure. When a piece of behaviour captures nothing from its environment, a plain function does the job, and Rust lets the two interchange in a useful way. A function pointer, written fn(&str) -> String, is the type of a bare function: just code, no captured state. Run this:
// When a closure captures nothing, it can stand in for a plain fn pointer, and
// a named fn can be passed anywhere an Fn closure is wanted. apply_all takes a
// fn pointer here, the simplest "callable" type, with no captured state at all.
fn shout(s: &str) -> String {
format!("{}!", s.to_uppercase())
}
fn apply_all(words: &[&str], f: fn(&str) -> String) -> Vec<String> {
let mut out = Vec::new();
for w in words {
out.push(f(w));
}
out
}
fn main() {
let words = ["go", "rust"];
// Pass a named fn by name: no parentheses, you are passing the function
// itself, not calling it.
println!("{:?}", apply_all(&words, shout));
// A closure that captures nothing coerces to the same fn pointer type.
println!("{:?}", apply_all(&words, |s| format!("<{s}>")));
}Two directions are worth seeing. First, you can pass a named function by name: apply_all(&words, shout) hands over shout itself, with no parentheses, because you are passing the function value, not calling it. Second, a closure that captures nothing, like |s| format!("<{s}>"), coerces automatically to the same fn pointer type, so it slots into the same parameter. A non-capturing closure and a bare function are interchangeable; the moment a closure captures even one variable, it is no longer a plain fn and must be taken via an Fn bound instead.
So when should you use which? Reach for a plain fn (or a non-capturing closure) when the behaviour is fixed and needs no context: it is the simplest callable, it has a nameable concrete type, and a function pointer is a single machine pointer with no captured data. Reach for a closure with an Fn/FnMut/FnOnce bound the instant you need to remember something from the surrounding scope, which, in practice, is most of the time. A function pointer is the degenerate closure: the one that remembered nothing.
It is tempting to type a callback parameter as fn(&str) -> String because it looks tidy, but that type accepts only bare functions and non-capturing closures. The moment a caller wants to pass a closure that captures a variable, it will not fit, because its captured data has nowhere to live in a single code pointer. If you want to accept any closure, take a generic F: Fn(...) instead; if you genuinely only want stateless callables, the fn type is the right, narrower choice.
Read it again
Scroll back to make_adder in section 05 and read it once more. Every piece has a name now. The |x| x + n is a closure, an anonymous function written inline. It remembers n by capturing it from the surrounding scope, and the move in front forces that capture to be by value, so the closure owns n and outlives make_adder. The return type impl Fn(i32) -> i32 promises “a closure of this shape”, and it is Fn rather than FnMut or FnOnce because the body only reads n, so the closure may be called many times, read only. You can read it now.
A closure is a function plus the slice of scope it captured. The body decides the capture mode (borrow, mutable borrow, or move) and which of Fn, FnMut, FnOnce it implements; move forces capture by value; you take a closure with a generic Fn-family bound and return one with impl Fn; and a closure that captures nothing is just a plain fn wearing different clothes.
One thread runs ahead of this chapter, and it is where closures finally pay off. The next chapter, on iterators, is built almost entirely on closures: map, filter, fold, and their kin all take a closure and apply it lazily across a sequence. The standard library is full of methods shaped like the retain you wrote here. Everything you learned about capture modes and the three traits is exactly what makes those methods compose, so closures were never the destination; they are the tool you carry into the next chapter.
The whole chapter in seven lines
- A closure is an anonymous function written inline,
|params| body, with its parameter and return types usually inferred. - A closure captures the variables in the scope where it was written, so it remembers context a plain
fncannot. - The body decides the capture mode: read borrows by
&, mutate borrows by&mut, consume takes ownership, the three relationships from III.02 and III.03. moveforces every capture to be by value, which you need when the closure must outlive the scope (a thread, or a returned closure).- The three traits
FnOnce(call once, may consume),FnMut(call repeatedly, may mutate), andFn(call repeatedly, read only) nest:Fn:FnMut:FnOnce. - Take a closure with a generic
Fn-family bound (orBox<dyn Fn>) and return one withimpl Fn(...), since a closure’s real type is unnameable. - A closure that captures nothing is a plain
fnpointer; the moment it captures anything, it is not.