Closures
Working with collections · Lesson 30
Closures are anonymous functions written |args| body. They can use variables from around them.
Coming from Go|x: i32| x * factor is func(x int) int { return x * factor }, usually with the types left out. A closure that changes a captured variable must itself be let mut, and move makes it take ownership of what it uses.
Coming from Kotlin|x| x * factor is { x: Int -> x * factor }. A closure that changes a captured variable must be stored in a let mut, and while it's still going to be used, you can't read that variable yourself.
Coming from PythonA closure is a lambda that can hold a whole block with || { ... }. Python makes you say nonlocal to change a captured variable; Rust makes you say mut: the closure itself must be let mut.
Coming from TypeScript|x| x * factor is the arrow function (x) => x * factor. A closure that changes a captured variable must be let mut, and while it's still going to be used, nothing else can read or write that variable.
Coming from Java|x| x * factor is a lambda, but Rust closures may change captured locals. Java forbids that; the workaround is an AtomicInteger, a one-element array or a field. A closure that changes something must itself be declared let mut.
Coming from C#|x| x * factor is a lambda like x => x * factor, and like C# it can change captured variables. Rust adds two rules: a closure that changes state must be let mut, and while it holds that borrow nobody else can use count.
Coming from CC has no closures; you'd pass a function pointer plus a void * for context. A Rust closure bundles both, captures variables by itself, and is type-checked, so there's no cast from void *.
Coming from C++A closure is a lambda that works out its own capture list: it borrows what it reads, borrows mutably what it changes, and move takes ownership. Rust needs let mut for a closure that changes what it captured even when it changes it through a reference, where a C++ [&] lambda needs nothing.
The quick comparison
fn main() { let factor = 3; let times = |x: i32| x * factor; // reads factor println!("{}", times(5));
let mut count = 0; let mut inc = || count += 1; // changes count, so inc is `mut` inc(); inc(); println!("count={count}");}15 count=2
package main
import "fmt"
func main() { factor := 3 times := func(x int) int { return x * factor } fmt.Println(times(5))
count := 0 inc := func() { count++ } inc() inc() fmt.Printf("count=%d\n", count)}15 count=2
fun main() { val factor = 3 val times = { x: Int -> x * factor } println(times(5))
var count = 0 val inc = { count++ } inc() inc() println("count=$count")}15 count=2
factor = 3times = lambda x: x * factor # reads factorprint(times(5))
count = 0
def inc() -> None: global count # needed to change a variable from outside count += 1
inc()inc()print(f"count={count}")15 count=2
const factor = 3;const times = (x: number) => x * factor; // reads factorconsole.log(times(5));
let count = 0;const inc = () => { count += 1; }; // changes countinc();inc();console.log(`count=${count}`);15 count=2
import java.util.concurrent.atomic.AtomicInteger;import java.util.function.IntUnaryOperator;
public class Main { public static void main(String[] args) { int factor = 3; IntUnaryOperator times = x -> x * factor; // reads factor System.out.println(times.applyAsInt(5));
// a lambda can't reassign a local, so keep the count in an object var count = new AtomicInteger(); Runnable inc = count::incrementAndGet; inc.run(); inc.run(); System.out.println("count=" + count); }}15 count=2
var factor = 3;var times = (int x) => x * factor; // reads factorConsole.WriteLine(times(5));
var count = 0;var inc = () => count++; // changes countinc();inc();Console.WriteLine($"count={count}");15 count=2
#include <stdio.h>
// C has no closures. A function cannot capture local variables,// so you pass what it needs as extra arguments.int times(int x, int factor) { return x * factor;}
void inc(int *count) { // a pointer lets it change the caller's variable *count += 1;}
int main(void) { int factor = 3; printf("%d\n", times(5, factor));
int count = 0; inc(&count); inc(&count); printf("count=%d\n", count); return 0;}15 count=2
#include <iostream>
int main() { int factor = 3; auto times = [factor](int x) { return x * factor; }; // copies factor in std::cout << times(5) << "\n";
int count = 0; auto inc = [&count] { count += 1; }; // & captures count by reference inc(); inc(); std::cout << "count=" << count << "\n";}15 count=2
What's different
- A closure borrows what it uses.
timesholds a&factor, andincholds a&mut count. - While
incis still going to be used, nothing else can touchcount. That is the one-writer rule. Readingcountworks here becauseincis never used again after that line. move |x| ...makes the closure take ownership of what it uses instead of borrowing. A closure given tothread::spawnneeds it when it uses local variables, because the thread can outlive the function that started it (see Threads).- Rust works out from the body whether a closure is
Fn(only reads),FnMut(changes something) orFnOnce(gives something away). A function that takes a closure says which kind it accepts.
A changing closure without mut
Looks fine: in Go, Kotlin, TypeScript, C# and C++ it runs fine; in Python it crashes at runtime. Rust won't compile it.
Works in Go. In Rust it's just a rule.Works in Kotlin. In Rust it's just a rule.Broken in Python. Rust catches it.Works in TypeScript. In Rust it's just a rule.Java refuses this too. Same idea, different words.Works in C#. In Rust it's just a rule.No direct equivalent in C. Just a Rust rule.Works in C++. In Rust it's just a rule.fn main() { let mut count = 0; let inc = || count += 1; inc(); inc(); println!("count={count}");}package main
import "fmt"
func main() { count := 0 inc := func() { count++ } inc() inc() fmt.Printf("count=%d\n", count)}count=2
fun main() { var count = 0 val inc = { count++ } inc() inc() println("count=$count")}count=2
count = 0
def inc(): count += 1
inc()inc()print(f"count={count}")Traceback (most recent call last):
File "main.py", line 8, in <module>
inc()
~~~^^
File "main.py", line 5, in inc
count += 1
^^^^^
UnboundLocalError: cannot access local variable 'count' where it is not associated with a valuelet count = 0;const inc = () => { count += 1; };inc();inc();console.log(`count=${count}`);count=2
Java refuses this too, for a different reason: a lambda can't change a local variable it captures. Use an AtomicInteger or a field instead.
public class Main { public static void main(String[] args) { int count = 0; Runnable inc = () -> count += 1; inc.run(); inc.run(); System.out.println("count=" + count); }}Main.java:4: error: local variables referenced from a lambda expression must be final or effectively final
Runnable inc = () -> count += 1;
^
1 errorvar count = 0;var inc = () => count += 1;inc();inc();Console.WriteLine($"count={count}");count=2
C has no closures. A function that changes a counter takes a pointer to it, as in the lesson.
#include <iostream>
int main() { int count = 0; auto inc = [&] { count += 1; }; inc(); inc(); std::cout << "count=" << count << "\n";}count=2
What does the compiler say? Guess first, then open.
error[E0596]: cannot borrow `inc` as mutable, as it is not declared as mutable --> src/main.rs:3:9 | 3 | let inc = || count += 1; | ^^^ ----- calling `inc` requires mutable binding due to mutable borrow of `count` | | | not mutable 4 | inc(); | --- cannot borrow as mutable 5 | inc(); | --- cannot borrow as mutable | help: consider changing this to be mutable | 3 | let mut inc = || count += 1; | +++
Go runs this and prints count=2. Any closure can change the variables it captured, with nothing to mark.
Kotlin runs this and prints count=2. A lambda stored in a val can still change a captured var.
Python raises UnboundLocalError. Assigning to count inside inc makes it a new local variable, so you need global count (or nonlocal in a nested function).
Prints count=2. A TypeScript arrow function can change outer let variables with no extra marking.
Java refuses this too, for a different reason: a lambda can't change a local variable it captures. Use an AtomicInteger or a field instead.
C# lambdas can change captured variables with no extra marker, so this prints count=2.
C has no closures. A function that changes a counter takes a pointer to it, as in the lesson.
A [&] lambda can change count without any marker, so this prints count=2. Capture by value and C++ also wants a keyword: mutable.
inc carries a hidden &mut count inside it. Calling inc changes something, so inc itself counts as a value that changes.
It is the same rule as in Methods: anything that changes has to be marked mut, so you can spot every place where state changes.
The fix
Compiles and runs.
fn main() { let mut count = 0; let mut inc = || count += 1; inc(); inc(); println!("count={count}");}count=2
The toolkit
Outputs come from Linux on 64-bit ARM, Rust 1.99.1, edition 2024.
A closure is a function written inline as |parameters| body, with no name, that can use the variables around it (the syntax is covered earlier). It is a value: store it, pass it, return it, put it in a struct. Types are usually inferred from the first use.
What a closure captures
Using a variable from outside is called capturing it. Rust picks the least it can get away with, from what the body does:
fn main() { // reads `factor`: the closure holds a shared reference to it let factor = 3; let times = |x: i32| x * factor; println!("times(5): {}", times(5)); println!("factor, read beside the closure: {factor}");
// changes `count`: the closure holds a mutable reference to it let mut count = 0; let mut inc = || count += 1; inc(); inc(); println!("count after two calls: {count}");
// gives `name` away: the closure takes it let name = String::from("ferris"); let consume = || drop(name); consume(); println!("the closure has run once; it cannot run again");}times(5): 15 factor, read beside the closure: 3 count after two calls: 2 the closure has run once; it cannot run again
| The body… | The closure holds | Implements | Meanwhile the code outside can… |
|---|---|---|---|
only reads factor |
a shared reference, &factor |
Fn, FnMut, FnOnce |
read it, not change it |
changes count |
a mutable reference, &mut count |
FnMut, FnOnce |
not touch it at all |
moves name out (drop(name) takes a value and ends it) |
the value itself | FnOnce |
not use it: it was moved in |
These are the ordinary rules of borrowing, mutable borrows and ownership. The closure is a value that holds the references, so the borrow lasts as long as the closure is used. Calling inc changes what it holds, so the variable that holds it must be mut. (Implements is explained below.) Each row has its error:
fn main() { let mut x = 1; let show = || println!("x: {x}"); x = 2; show();}error[E0506]: cannot assign to `x` because it is borrowed --> src/main.rs:4:5 | 3 | let show = || println!("x: {x}"); | -- - borrow occurs due to use in closure | | | `x` is borrowed here 4 | x = 2; | ^^^^^ `x` is assigned to here but it was already borrowed 5 | show(); | ---- borrow later used here
fn main() { let mut count = 0; let mut inc = || count += 1; inc(); println!("count: {count}"); inc();}error[E0502]: cannot borrow `count` as immutable because it is also borrowed as mutable --> src/main.rs:5:23 | 3 | let mut inc = || count += 1; | -- ----- first borrow occurs due to use of `count` in closure | | | mutable borrow occurs here 4 | inc(); 5 | println!("count: {count}"); | ^^^^^ immutable borrow occurs here 6 | inc(); | --- mutable borrow later used here
fn main() { let name = String::from("ferris"); let consume = || drop(name); consume(); println!("{name}");}error[E0382]: borrow of moved value: `name` --> src/main.rs:5:16 | 2 | let name = String::from("ferris"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait 3 | let consume = || drop(name); | -- ---- variable moved due to use in closure | | | value moved into closure here 4 | consume(); 5 | println!("{name}"); | ^^^^ value borrowed here after move | help: consider cloning the value if the performance cost is acceptable | 3 | let consume = || drop(name.clone()); | ++++++++
Reading count after the last call to inc, as the first program does, is fine.
move: take ownership
move before the bars makes the closure take every variable it uses by value, whatever the body does. A number is copied, so the closure gets its own:
fn main() { let mut count = 0; let mut inc = move || { count += 1; count }; println!("closure's count: {}", inc()); println!("closure's count: {}", inc()); println!("outer count: {count}");}closure's count: 1 closure's count: 2 outer count: 0
A String is moved in, so the outside loses it. move does not make a closure run once: this one only reads its String.
fn main() { let name = String::from("ferris"); let greet = move || println!("hello, {name}"); greet(); greet();}hello, ferris hello, ferris
fn main() { let name = String::from("ferris"); let greet = move || println!("hello, {name}"); greet(); println!("{name}");}error[E0382]: borrow of moved value: `name` --> src/main.rs:5:16 | 2 | let name = String::from("ferris"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait 3 | let greet = move || println!("hello, {name}"); | ------- ---- variable moved due to use in closure | | | value moved into closure here 4 | greet(); 5 | println!("{name}"); | ^^^^ value borrowed here after move | help: consider cloning the value before moving it into the closure | 3 ~ let value = name.clone(); 4 ~ let greet = move || println!("hello, {value}");
A garbage-collected language keeps a captured variable alive as long as any closure uses it. Rust has no collector and frees a function’s locals when it returns, so a closure that outlives its function must own what it uses. That is what move is for. A thread started with thread::spawn may outlive the function that started it, so it cannot borrow that function’s locals (Threads):
use std::thread;
fn main() { let name = String::from("ferris"); let handle = thread::spawn(|| println!("hello, {name}")); handle.join().unwrap();}error[E0373]: closure may outlive the current function, but it borrows `name`, which is owned by the current function --> src/main.rs:5:32 | 5 | let handle = thread::spawn(|| println!("hello, {name}")); | ^^ ---- `name` is borrowed here | | | may outlive borrowed value `name` | note: function requires argument type to outlive `'static` --> src/main.rs:5:18 | 5 | let handle = thread::spawn(|| println!("hello, {name}")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: to force the closure to take ownership of `name` (and any other referenced variables), use the `move` keyword | 5 | let handle = thread::spawn(move || println!("hello, {name}")); | ++++
use std::thread;
fn main() { let name = String::from("ferris"); let handle = thread::spawn(move || println!("hello from a thread, {name}")); handle.join().unwrap();}hello from a thread, ferris
The same error appears when a function returns a closure (below); both times the help line suggests move.
Fn, FnMut and FnOnce
The compiler gives every closure some of three traits (a trait is a named ability; traits comes later), as the table’s third column shows. Fn can be called many times, even through a shared reference; FnMut many times from a mut variable; FnOnce once. Which a closure gets depends on what its body does with what it captured, not on move.
You write the traits on the function that receives the closure, as the kind it accepts (impl Fn() means “any closure of that kind”; below has the details):
fn call_twice(f: impl Fn()) { f(); f();}
fn call_twice_mut(mut f: impl FnMut()) { f(); f();}
fn call_once(f: impl FnOnce()) { f();}
fn main() { let name = String::from("ferris"); let mut hits = 0;
// only reads `name`: fits all three call_twice(|| println!("hello, {name}")); call_twice_mut(|| println!("hello, {name}")); call_once(|| println!("hello, {name}"));
// changes `hits`: fits FnMut and FnOnce, not Fn call_twice_mut(|| hits += 1); println!("hits: {hits}"); call_once(|| hits += 10); println!("hits: {hits}");
// gives `name` away: fits only FnOnce call_once(|| drop(name));}hello, ferris hello, ferris hello, ferris hello, ferris hello, ferris hits: 2 hits: 12
The other directions fail. A closure that gives something away can run once:
fn main() { let name = String::from("ferris"); let consume = || drop(name); consume(); consume();}error[E0382]: use of moved value: `consume` --> src/main.rs:5:5 | 4 | consume(); | --------- `consume` moved due to this call 5 | consume(); | ^^^^^^^ value used here after move | note: closure cannot be invoked more than once because it moves the variable `name` out of its environment --> src/main.rs:3:27 | 3 | let consume = || drop(name); | ^^^^ note: this value implements `FnOnce`, which causes it to be moved when called --> src/main.rs:4:5 | 4 | consume(); | ^^^^^^^
A function that asks for Fn rejects closures that change or give away what they captured:
fn call_twice(f: impl Fn()) { f(); f();}
fn main() { let mut count = 0; call_twice(|| count += 1); println!("count: {count}");}error[E0594]: cannot assign to `count`, as it is a captured variable in a `Fn` closure --> src/main.rs:8:19 | 1 | fn call_twice(f: impl Fn()) { | --------- change this to accept `FnMut` instead of `Fn` ... 7 | let mut count = 0; | --------- `count` declared here, outside the closure 8 | call_twice(|| count += 1); | ---------- -- ^^^^^^^^^^ cannot assign | | | | | in this closure | expects `Fn` instead of `FnMut`
fn call_twice(f: impl Fn()) { f(); f();}
fn main() { let name = String::from("ferris"); call_twice(|| drop(name));}error[E0507]: cannot move out of `name`, a captured variable in an `Fn` closure --> src/main.rs:8:24 | 7 | let name = String::from("ferris"); | ---- ---------------------- move occurs because `name` has type `String`, which does not implement the `Copy` trait | | | captured outer variable 8 | call_twice(|| drop(name)); | -- ^^^^ `name` is moved here | | | captured by this `Fn` closure | help: `Fn` and `FnMut` closures require captured values to be able to be consumed multiple times, but `FnOnce` closures may consume them only once --> src/main.rs:1:23 | 1 | fn call_twice(f: impl Fn()) { | ^^^^ help: consider cloning the value if the performance cost is acceptable | 8 | call_twice(|| drop(name.clone())); | ++++++++
Passing a closure to a function
fn apply(f: impl Fn(i32) -> i32, x: i32) -> i32 { f(x)}
fn apply_generic<F>(f: F, x: i32) -> i32where F: Fn(i32) -> i32,{ f(x)}
fn apply_dyn(f: &dyn Fn(i32) -> i32, x: i32) -> i32 { f(x)}
fn apply_ptr(f: fn(i32) -> i32, x: i32) -> i32 { f(x)}
fn repeat(n: u32, mut f: impl FnMut()) { for _ in 0..n { f(); }}
fn double(x: i32) -> i32 { x * 2}
fn main() { let offset = 10; println!("apply: {}", apply(|x| x + offset, 5)); println!("apply_generic: {}", apply_generic(|x| x + offset, 5)); println!("apply_dyn: {}", apply_dyn(&|x| x + offset, 5)); println!("apply with a named fn: {}", apply(double, 5)); println!("apply_ptr, named fn: {}", apply_ptr(double, 5)); println!( "apply_ptr, closure with no captures: {}", apply_ptr(|x| x - 1, 5) );
let mut calls = 0; repeat(3, || calls += 1); println!("repeat(3) made {calls} calls"); repeat(0, || calls += 1); println!("repeat(0) made {calls} calls in total");}apply: 15 apply_generic: 15 apply_dyn: 15 apply with a named fn: 10 apply_ptr, named fn: 10 apply_ptr, closure with no captures: 4 repeat(3) made 3 calls repeat(0) made 3 calls in total
impl Fn(i32) -> i32in a parameter means “any type that implements this”.F: Fn(i32) -> i32withwheresays the same by naming a type parameterFlimited to such closures, (reading signatures, generics).&dyn Fn(i32) -> i32is one compiled function that takes any closure of that shape through a pointer (trait objects).fn(i32) -> i32is a plain function pointer. It accepts named functions and closures that capture nothing, and refuses the rest:
fn apply_ptr(f: fn(i32) -> i32, x: i32) -> i32 { f(x)}
fn main() { let offset = 10; println!("{}", apply_ptr(|x| x + offset, 5));}error[E0308]: mismatched types --> src/main.rs:7:30 | 7 | println!("{}", apply_ptr(|x| x + offset, 5)); | --------- ^^^^^^^^^^^^^^ expected fn pointer, found closure | | | arguments to this function are incorrect | = note: expected fn pointer `fn(i32) -> i32` found closure `{closure@src/main.rs:7:30: 7:33}` note: closures can only be coerced to `fn` types if they do not capture any variables --> src/main.rs:7:38 | 7 | println!("{}", apply_ptr(|x| x + offset, 5)); | ^^^^^^ `offset` captured here note: function defined here --> src/main.rs:1:4 | 1 | fn apply_ptr(f: fn(i32) -> i32, x: i32) -> i32 { | ^^^^^^^^^ -----------------
Ask for the most permissive trait that does the job: FnOnce accepts every closure in the table above, FnMut all but those that give captures away, Fn only those that just read. Ask for FnOnce if you call it once, FnMut if you call it several times (and declare the parameter mut f), and Fn if you must call it through a shared reference, such as &self.
A closure passed by value moves into the function; one that owns a String is not Copy:
fn call_twice(f: impl Fn()) { f(); f();}
fn main() { let name = String::from("ferris"); let greet = move || println!("hello, {name}"); call_twice(greet); call_twice(greet);}error[E0382]: use of moved value: `greet` --> src/main.rs:10:16 | 8 | let greet = move || println!("hello, {name}"); | ----- move occurs because `greet` has type `{closure@src/main.rs:8:17: 8:24}`, which does not implement the `Copy` trait 9 | call_twice(greet); | ----- value moved here 10 | call_twice(greet); | ^^^^^ value used here after move | help: consider borrowing `greet` | 9 | call_twice(&greet); | +
Lend it instead. A reference to an Fn closure is itself an Fn:
fn call_twice(f: impl Fn()) { f(); f();}
fn main() { let name = String::from("ferris"); let greet = move || println!("hello, {name}"); call_twice(&greet); // lend it: `greet` is still ours call_twice(&greet); greet();}hello, ferris hello, ferris hello, ferris hello, ferris hello, ferris
Returning closures
A closure’s type has no name you can write, so a function returns it as impl Fn(...), “some one type that implements this”.
fn make_adder(n: i32) -> impl Fn(i32) -> i32 { move |x| x + n}
fn make_counter() -> impl FnMut() -> u32 { let mut count = 0; move || { count += 1; count }}
fn pick(op: &str) -> Box<dyn Fn(i32, i32) -> i32> { match op { "add" => Box::new(|a, b| a + b), "mul" => Box::new(|a, b| a * b), _ => Box::new(|_, _| 0), }}
fn main() { let add5 = make_adder(5); let add10 = make_adder(10); println!("add5(1): {}, add10(1): {}", add5(1), add10(1));
let mut a = make_counter(); let mut b = make_counter(); println!("a: {} {}, b: {}", a(), a(), b());
for op in ["add", "mul", "other"] { println!("{op}(3, 4): {}", pick(op)(3, 4)); }}add5(1): 6, add10(1): 11 a: 1 2, b: 1 add(3, 4): 7 mul(3, 4): 12 other(3, 4): 0
The closure must own what it uses, because the function’s locals are gone when it returns. Without move:
fn make_adder(n: i32) -> impl Fn(i32) -> i32 { |x| x + n}
fn main() { println!("{}", make_adder(5)(1));}error[E0373]: closure may outlive the current function, but it borrows `n`, which is owned by the current function --> src/main.rs:2:5 | 2 | |x| x + n | ^^^ - `n` is borrowed here | | | may outlive borrowed value `n` | note: closure is returned here --> src/main.rs:2:5 | 2 | |x| x + n | ^^^^^^^^^ help: to force the closure to take ownership of `n` (and any other referenced variables), use the `move` keyword | 2 | move |x| x + n | ++++
Each call to make_counter makes a closure with its own count. impl Fn names one type and every closure expression has its own, so two closures written separately cannot come out of one if (unless neither captures anything, below):
fn pick(add: bool, n: i32) -> impl Fn(i32) -> i32 { if add { move |x| x + n } else { move |x| x * n }}
fn main() { println!("{}", pick(true, 5)(1));}error[E0308]: `if` and `else` have incompatible types --> src/main.rs:2:38 | 2 | if add { move |x| x + n } else { move |x| x * n } | -------------- ^^^^^^^^^^^^^^ expected closure, found a different closure | | | the expected closure | expected because of this | = note: expected closure `{closure@src/main.rs:2:14: 2:22}` found closure `{closure@src/main.rs:2:38: 2:46}` = note: no two closures, even if identical, have the same type = help: consider boxing your closure and/or using it as a trait object help: you could change the return type to be a boxed trait object | 1 - fn pick(add: bool, n: i32) -> impl Fn(i32) -> i32 { 1 + fn pick(add: bool, n: i32) -> Box<dyn Fn(i32) -> i32> { | help: if you change the return type to expect trait objects, box the returned expressions | 2 | if add { Box::new(move |x| x + n) } else { Box::new(move |x| x * n) } | +++++++++ + +++++++++ +
The fix is Box<dyn Fn(...)>, as pick does. Box<T> is an owned pointer to a value on the heap (Smart pointers). dyn Fn(i32, i32) -> i32 means “some closure with this signature, found at run time” (trait objects). One type then holds any closure of that shape, which a Vec of different closures also needs. A stored boxed closure must own what it uses, written 'static (lifetimes); the registry exercise meets it.
Closures that capture nothing are the exception. Each coerces to a fn pointer, so they can share a type:
// Neither closure captures anything, so each one coerces to a plain `fn` pointer// and the two arms have the same type.fn pick(add: bool) -> fn(i32) -> i32 { if add { |x| x + 1 } else { |x| x * 2 }}
fn main() { println!("pick(true)(5): {}", pick(true)(5)); println!("pick(false)(5): {}", pick(false)(5)); let both = [|x: i32| x + 1, |x: i32| x * 2]; println!("both[1](5): {}", both[1](5));}pick(true)(5): 6 pick(false)(5): 10 both[1](5): 10
Closures in structs, and callbacks
A struct holds a closure through a type parameter (F below: a placeholder the compiler fills in with the closure’s type; generics) when it has one, or through Box<dyn FnMut> (or Box<dyn Fn>) when it has several of different types:
// One closure type per struct: the type parameter `F` is that closure's type.struct Button<F: Fn(&str)> { label: String, on_click: F,}
impl<F: Fn(&str)> Button<F> { fn click(&self) { (self.on_click)(&self.label); }}
// Many closures of different types: box each one.struct Events { handlers: Vec<Box<dyn FnMut(&str) -> usize>>,}
impl Events { fn fire(&mut self, event: &str) -> Vec<usize> { self.handlers.iter_mut().map(|h| h(event)).collect() }}
fn main() { let prefix = String::from("clicked"); let button = Button { label: String::from("OK"), on_click: |label| println!("{prefix}: {label}"), }; button.click(); button.click();
let mut seen = 0; let mut events = Events { handlers: Vec::new(), }; events.handlers.push(Box::new(|e| e.len())); events.handlers.push(Box::new(move |_| { seen += 1; seen })); println!("fire 1: {:?}", events.fire("start")); println!("fire 2: {:?}", events.fire("stop")); println!("outer seen (the closure counts its own copy): {seen}");}clicked: OK clicked: OK fire 1: [5, 1] fire 2: [4, 2] outer seen (the closure counts its own copy): 0
A closure in a field is called with parentheses around the field, (self.on_click)(...). Without them Rust looks for a method:
struct Button<F: Fn(&str)> { on_click: F,}
fn main() { let button = Button { on_click: |label| println!("clicked {label}"), }; button.on_click("OK");}error[E0599]: no method named `on_click` found for struct `Button<F>` in the current scope --> src/main.rs:9:12 | 1 | struct Button<F: Fn(&str)> { | -------------------------- method `on_click` not found for this struct ... 9 | button.on_click("OK"); | ^^^^^^^^ field, not a method | help: to call the closure stored in `on_click`, surround the field access with parentheses | 9 | (button.on_click)("OK"); | + +
Box<dyn FnOnce() -> String> holds a job that runs once (thread::spawn takes an FnOnce too). Looping over the Vec by value gives each job away as it is run:
fn main() { let name = String::from("ferris"); let mut jobs: Vec<Box<dyn FnOnce() -> String>> = Vec::new(); jobs.push(Box::new(move || name)); // hands `name` back: it can run only once jobs.push(Box::new(|| String::from("second job")));
for job in jobs { println!("job returned: {}", job()); }}job returned: ferris job returned: second job
In a loop, each pass has its own i, which ends with the pass, so the closure must own a copy:
fn main() { let mut fs: Vec<Box<dyn Fn() -> i32>> = Vec::new(); for i in 0..3 { fs.push(Box::new(move || i)); } for f in &fs { print!("{} ", f()); } println!();}0 1 2
fn main() { let mut fs: Vec<Box<dyn Fn() -> i32>> = Vec::new(); for i in 0..3 { fs.push(Box::new(|| i)); } for f in &fs { print!("{} ", f()); } println!();}error[E0597]: `i` does not live long enough --> src/main.rs:4:29 | 3 | for i in 0..3 { | - binding `i` declared here 4 | fs.push(Box::new(|| i)); | -- -- ^ borrowed value does not live long enough | | | | | value captured here | borrow later used here 5 | } | - `i` dropped here while still borrowed
What a closure cannot do
Its parameter types are fixed by the first use. A closure is not generic like a function:
fn main() { let identity = |x| x; println!("{}", identity(1)); println!("{}", identity("one"));}error[E0308]: mismatched types --> src/main.rs:4:29 | 4 | println!("{}", identity("one")); | -------- ^^^^^ expected integer, found `&str` | | | arguments to this function are incorrect | note: expected because the closure was earlier called with an argument of type `{integer}` --> src/main.rs:3:29 | 3 | println!("{}", identity(1)); | -------- ^ expected because this argument is of type `{integer}` | | | in this closure call note: closure parameter defined here --> src/main.rs:2:21 | 2 | let identity = |x| x; | ^
It cannot call itself by name. The name factorial does not exist until the let is finished, and the closure has no name of its own. Write a recursive helper as a nested fn, which can see its own name and captures nothing:
fn main() { let factorial = |n: u64| if n <= 1 { 1 } else { n * factorial(n - 1) }; println!("{}", factorial(5));}error[E0425]: cannot find function `factorial` in this scope --> src/main.rs:2:57 | 2 | let factorial = |n: u64| if n <= 1 { 1 } else { n * factorial(n - 1) }; | ^^^^^^^^^ not found in this scope
fn main() { // A function item has a name, so it can call itself. fn factorial(n: u64) -> u64 { if n <= 1 { 1 } else { n * factorial(n - 1) } } println!("factorial(5): {}", factorial(5));}factorial(5): 120
Closures in the standard library
Closures are how you hand a rule to a method: sort_by_key takes one that works out a key, sort_by one that compares two items, retain one that says whether to keep an item. Iterator adapters such as map and filter take closures the same way.
#[derive(Debug)]struct Person { name: String, age: u32,}
fn person(name: &str, age: u32) -> Person { Person { name: name.to_string(), age, }}
fn main() { let mut people = vec![person("Cy", 40), person("Ana", 31), person("Bo", 25)];
people.sort_by_key(|p| p.age); println!( "by age: {:?}", people.iter().map(|p| &p.name).collect::<Vec<_>>() );
people.sort_by(|a, b| a.name.cmp(&b.name)); println!( "by name: {:?}", people.iter().map(|p| &p.name).collect::<Vec<_>>() );
let min_age = 30; people.retain(|p| p.age >= min_age); println!( "aged {min_age}+: {:?}", people.iter().map(|p| &p.name).collect::<Vec<_>>() );
let total: u32 = people.iter().map(|p| p.age).sum(); println!("total age: {total}");}by age: ["Bo", "Ana", "Cy"] by name: ["Ana", "Bo", "Cy"] aged 30+: ["Ana", "Cy"] total age: 71
sort_by_key has a trap: the key may not borrow from the item.
struct Person { name: String,}
fn main() { let mut people = vec![ Person { name: String::from("Cy"), }, Person { name: String::from("Ana"), }, ]; people.sort_by_key(|p| &p.name); println!("{}", people[0].name);}error: lifetime may not live long enough --> src/main.rs:14:28 | 14 | people.sort_by_key(|p| &p.name); | -- ^^^^^^^ returning this value requires that `'1` must outlive `'2` | || | |return type of closure is &'2 String | has type `&'1 Person`
Use sort_by(|a, b| a.name.cmp(&b.name)), as above, or return a copy such as p.name.clone().
A closure also delays work. unwrap_or(x) computes x before the call, whether or not it is used. unwrap_or_else(f) runs f only for None (ok_or_else and or_insert_with follow the same pattern):
fn default_port() -> u16 { println!(" (default_port ran)"); 8080}
fn main() { let configured: Option<u16> = Some(443);
println!("unwrap_or:"); let a = configured.unwrap_or(default_port()); println!(" -> {a}");
println!("unwrap_or_else:"); let b = configured.unwrap_or_else(default_port); println!(" -> {b}");
println!("unwrap_or_else with nothing configured:"); let missing: Option<u16> = None; let c = missing.unwrap_or_else(|| default_port() + 1); println!(" -> {c}");}unwrap_or: (default_port ran) -> 443 unwrap_or_else: -> 443 unwrap_or_else with nothing configured: (default_port ran) -> 8081
Which form to write
| You are… | Write |
|---|---|
handing a rule to a method you call right now (sort_by_key, retain, map) |
a plain closure; no move |
a thread::spawn thread, a returned closure, or one kept after the function ends |
move |args| ... |
| a function that takes a callback | the trait that matches how you call it (above) |
| one closure in a struct; several different ones | a type parameter limited to Fn(..); Box<dyn Fn(..)> in a Vec |
When the compiler complains about a closure, the message points at the capture (x is borrowed here, value moved into closure here) and usually offers move or FnMut. Read which variable it names first: move on a closure that should share a counter gives it a private copy.
Your closures in Rust
Coming from Python or TypeScript
| In Python or TypeScript | In Rust |
|---|---|
lambda x: x + n (one expression) |
|x| x + n, or |x| { ... } with statements |
def make_adder(n): return lambda x: x + n |
fn make_adder(n: i32) -> impl Fn(i32) -> i32 { move |x| x + n } |
(x: number) => x + n |
|x| x + n |
def inc(): global count; count += 1 (nonlocal in a nested function) |
let mut inc = || count += 1;: no global/nonlocal declaration, but the closure variable is mut |
sorted(people, key=lambda p: p.age) |
people.sort_by_key(|p| p.age); a key that borrows fails (below) |
Python makes you say global or nonlocal to change a captured variable; Rust makes you say mut. Both languages capture the variable, so a closure sees later changes, and keep it alive as long as the closure lives; Rust has no garbage collector, so a closure that outlives its function must own what it uses (move). Python loops are the one real surprise:
Coming from Python
fs = [lambda: i for i in range(3)]print("late binding:", [f() for f in fs])
gs = [lambda i=i: i for i in range(3)]print("default argument:", [g() for g in gs])
x = 1show = lambda: print("x:", x)x = 2show()late binding: [2, 2, 2] default argument: [0, 1, 2] x: 2
The loop closures all read the one i, which is 2 at the end. The default argument i=i evaluates i when the lambda is made, which is what a move closure does with a number in Rust. A closure has no default or keyword arguments in Rust.
Coming from TypeScript
const withVar: (() => number)[] = [];for (var i = 0; i < 3; i++) withVar.push(() => i);console.log("var:", withVar.map((f) => f()));
const withLet: (() => number)[] = [];for (let j = 0; j < 3; j++) withLet.push(() => j);console.log("let:", withLet.map((f) => f()));
let x = 1;const show = () => console.log("x:", x);x = 2;show();var: [ 3, 3, 3 ] let: [ 0, 1, 2 ] x: 2
A var loop has one variable for all passes (the older form); a let loop has one per pass, as in Rust.
In Rust, changing x while a borrowing closure is alive is the first error above. move copies a number (the i=i trick) but moves anything else: the closure owns a String and the outside can no longer use it.
Coming from C or C++
Coming from C
C has no closures. The usual stand-in is a function pointer plus a void * that carries the data, with a cast inside. A Rust closure does the same job as that pair, packaged and type-checked:
#include <stdio.h>
// A C "closure": a function pointer plus a void * that carries the captured data.typedef struct { int total;} Sum;
static void add(void *ctx, int x) { Sum *s = ctx; // the cast is the programmer's promise s->total += x;}
static void for_each(const int *xs, int n, void (*f)(void *, int), void *ctx) { for (int i = 0; i < n; i++) f(ctx, xs[i]);}
int main(void) { int xs[] = {1, 2, 3}; Sum s = {0}; for_each(xs, 3, add, &s); printf("total: %d\n", s.total); return 0;}total: 6
A plain fn pointer in Rust accepts only closures that capture nothing (the error above).
Coming from C++
A lambda’s capture list is written out. Rust has none: the body decides, and move is the nearest thing to [=].
| In C++ | In Rust |
|---|---|
[=] |
move (every variable the body uses, by value) |
[&], [&x] |
the default: each variable by the least the body needs |
[x], [x = std::move(s)] |
a move closure (a value that is not Copy is moved, not copied) |
[x]() mutable { ... } |
a move closure that changes its copy, held in a let mut variable |
[x, &y] |
no spelling: move is all or nothing. Make the reference first, then move both |
std::function<int(int)> |
Box<dyn FnMut(i32) -> i32> (Fn if it never changes state) |
a template parameter F |
impl Fn(i32) -> i32 |
[](auto x) { ... } |
no generic closures: write a generic fn |
#include <functional>#include <iostream>#include <string>#include <utility>
int main() { int x = 1; auto by_value = [x] { return x; }; auto by_ref = [&x] { return x; }; auto by_value_mutable = [x]() mutable { return ++x; }; x = 2; std::cout << "[x]: " << by_value() << ", [&x]: " << by_ref() << "\n"; std::cout << "[x] mutable: " << by_value_mutable() << " then " << by_value_mutable() << ", x is " << x << "\n";
std::string s = "ferris"; auto owns = [s = std::move(s)] { return s.size(); }; std::cout << "moved in: " << owns() << ", s.size() now " << s.size() << "\n";
std::function<int()> counter = [n = 0]() mutable { return ++n; }; std::cout << "std::function holding a mutable lambda: " << counter() << " then " << counter() << "\n";
int y = 10; auto mixed = [x, &y] { y += x; return y; }; std::cout << "[x, &y]: " << mixed() << ", y is " << y << "\n";}[x]: 1, [&x]: 2 [x] mutable: 2 then 3, x is 2 moved in: 6, s.size() now 0 std::function holding a mutable lambda: 1 then 2 [x, &y]: 12, y is 12
std::function can hold a mutable lambda, so the match is FnMut, not Fn. For [x, &y], bind the reference and move it in:
fn main() { let x = 2; let mut y = 1; // `move` takes every variable the body uses, so make the reference first: // the closure then owns `x` (a copy) and the reference to `y`. let y_ref = &mut y; let mut add = move || { *y_ref += x; *y_ref }; println!("add(): {}", add()); println!("add(): {}", add()); println!("y after the closure's last use: {y}");}add(): 3 add(): 5 y after the closure's last use: 5
A lambda that captures by reference and outlives the variable compiles. Compiled with -fsanitize=address, the call is reported:
#include <functional>#include <iostream>
std::function<int(int)> make_adder(int n) { return [&](int x) { return x + n; }; // n dies when make_adder returns}
int main() { auto add5 = make_adder(5); std::cout << add5(1) << "\n";}=================================================================
==1==ERROR: AddressSanitizer: stack-use-after-return on address 0x············ at pc 0x············ bp 0x············ sp 0x············
READ of size 4 at 0x············ thread T0
#0 0x············ in operator() main.cpp:5
#1 0x············ in __invoke_impl<int, make_adder(int)::<lambda(int)>&, int> /usr/include/c++/14/bits/invoke.h:61
#2 0x············ in __invoke_r<int, make_adder(int)::<lambda(int)>&, int> /usr/include/c++/14/bits/invoke.h:114
#3 0x············ in _M_invoke /usr/include/c++/14/bits/std_function.h:290
#4 0x············ in std::function<int (int)>::operator()(int) const /usr/include/c++/14/bits/std_function.h:591
#5 0x············ in main main.cpp:10
#6 0x············ (/lib/aarch64-linux-gnu/libc.so.6+0x22258)
#7 0x············ in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x22338)
#8 0x············ in _start (main+0x15ec)
Address 0x············ is located in stack of thread T0 at offset 48 in frame
#0 0x············ in make_adder(int) main.cpp:4
This frame has 2 object(s):
[48, 52) 'n' (line 4) <== Memory access at offset 48 is inside this variable
[64, 72) '<unknown>'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-return main.cpp:5 in operator()
Shadow bytes around the buggy address:
0x············: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x············: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x············: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x············: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x············: f1 f1 f1 f1 00 00 00 00 f3 f3 f3 f3 00 00 00 00
=>0x············: f5 f5 f5 f5 f5 f5[f5]f5 f5 f5 f5 f5 00 00 00 00
0x············: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x············: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x············: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x············: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x············: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==1==ABORTINGRust refuses the same function (E0373, the make_adder error above).
Going deeper
A closure is a struct
The compiler turns a closure into a value that holds its captures. Sizes show what it holds:
use std::mem::{size_of, size_of_val};
fn main() { let factor = 3; let mut count = 0; let name = String::from("ferris"); println!("name: len {}, capacity {}", name.len(), name.capacity());
let no_captures = |x: i32| x + 1; let reads_factor = |x: i32| x * factor; let bumps_count = || count += 1; let owns_factor = move |x: i32| x * factor; let owns_name = move || name.len();
println!( "size of a closure that captures nothing: {}", size_of_val(&no_captures) ); println!( "reads factor (shared ref): {}", size_of_val(&reads_factor) ); println!( "bumps count (mutable ref): {}", size_of_val(&bumps_count) ); println!( "owns factor (an i32): {}", size_of_val(&owns_factor) ); println!("owns name (a String): {}", size_of_val(&owns_name)); println!( "for comparison, &i32: {}, i32: {}, String: {}", size_of::<&i32>(), size_of::<i32>(), size_of::<String>() );}name: len 6, capacity 6 size of a closure that captures nothing: 0 reads factor (shared ref): 8 bumps count (mutable ref): 8 owns factor (an i32): 4 owns name (a String): 24 for comparison, &i32: 8, i32: 4, String: 24
A closure that captures nothing takes no space. One that holds a reference is the size of a reference, and one that owns a String is the size of a String. (The layout of closures is not promised; these are what this compiler prints.)
Every closure expression has its own type, even two identical ones, which is why separately written closures need Box<dyn Fn> to share a Vec.
Captures are exact
A closure captures the part of a variable it names. p.name borrows the field name, and the other fields stay free. This applies from the 2021 edition on (the runner builds edition 2024); in the 2018 edition p.score += 5 below is an error (E0506). The Edition Guide describes the change.
struct Player { name: String, score: u32,}
fn main() { let mut p = Player { name: String::from("Ana"), score: 0, };
// The closure uses only `p.name`, so it borrows only that field. let show = || println!("player {}", p.name); p.score += 5; // a different field: allowed while `show` is alive show(); println!("score: {}", p.score);}player Ana score: 5
Calling a method on the variable names all of it, because the method takes &self:
struct Player { name: String, score: u32,}
impl Player { fn label(&self) -> String { format!("player {}", self.name) }}
fn main() { let mut p = Player { name: String::from("Ana"), score: 0, };
let show = || println!("{}", p.label()); p.score += 5; show();}error[E0506]: cannot assign to `p.score` because it is borrowed --> src/main.rs:19:5 | 18 | let show = || println!("{}", p.label()); | -- - borrow occurs due to use in closure | | | `p.score` is borrowed here 19 | p.score += 5; | ^^^^^^^^^^^^ `p.score` is assigned to here but it was already borrowed 20 | show(); | ---- borrow later used here
Generic, dyn, or fn pointer
An impl Fn or F: Fn parameter is filled in with the closure’s own type, so the compiler knows which code each call runs (generics). A dyn Fn value is a pointer to the closure plus a pointer to a table of its methods, and a call goes through the table, so the compiler cannot always see which code runs (an optimised build sometimes can). The sizes show the two pointers; a fn pointer is one:
use std::mem::size_of;
fn main() { println!( "&dyn Fn(i32) -> i32: {}", size_of::<&dyn Fn(i32) -> i32>() ); println!( "Box<dyn Fn(i32) -> i32>: {}", size_of::<Box<dyn Fn(i32) -> i32>>() ); println!("fn(i32) -> i32: {}", size_of::<fn(i32) -> i32>()); println!("&i32 (for comparison): {}", size_of::<&i32>());}&dyn Fn(i32) -> i32: 16 Box<dyn Fn(i32) -> i32>: 16 fn(i32) -> i32: 8 &i32 (for comparison): 8
Reach for dyn when you store closures of different types together or choose one at run time; otherwise prefer impl Fn. No timings are claimed here: measure your own case.
Practice
Closures that return closures
Write make_adder, make_counter and compose. Each returns a closure that outlives the function, so each needs move: forgetting it is the error you are meant to meet. The checks interleave two counters (a static total fails) and compose in both orders.
// Three functions that return a closure. Each returned closure must keep working after// the function that made it has returned, so it has to own what it uses.
// A closure that adds `n` to its argument.fn make_adder(n: i32) -> impl Fn(i32) -> i32 { |_x| todo!("return a closure that adds `n`; it uses `n` after this function is gone")}
// A closure that returns a running total: each call adds `step` and returns the total.// Every counter made by this function has its own total.fn make_counter(step: i32) -> impl FnMut() -> i32 { || todo!("return a closure with its own `total`; each call adds `step` and returns it")}
// A closure that applies `first` to its argument and then `second` to the result.fn compose(first: impl Fn(i32) -> i32, second: impl Fn(i32) -> i32) -> impl Fn(i32) -> i32 { |_x| todo!("return a closure that calls both; it must own `first` and `second`")}
fn main() { let add2 = make_adder(2); let add10 = make_adder(10); assert_eq!(add2(1), 3); assert_eq!(add10(1), 11);
// Two counters made by the same function do not share a total, even when their // calls are interleaved. let mut by_one = make_counter(1); let mut by_five = make_counter(5); assert_eq!(by_one(), 1); assert_eq!(by_one(), 2); assert_eq!(by_five(), 5); assert_eq!(by_one(), 3); assert_eq!(by_five(), 10);
let add_then_double = compose(make_adder(1), |x| x * 2); let double_then_add = compose(|x| x * 2, make_adder(1)); assert_eq!(add_then_double(5), 12); assert_eq!(double_then_add(5), 11);
// A composed closure can be composed again. let add3 = compose(make_adder(1), make_adder(2)); let add_then_double_then_negate = compose(add_then_double, |x| -x); assert_eq!(add3(0), 3); assert_eq!(add_then_double_then_negate(5), -12); println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
// Three functions that return a closure. Each returned closure must keep working after// the function that made it has returned, so it has to own what it uses.
// A closure that adds `n` to its argument.fn make_adder(n: i32) -> impl Fn(i32) -> i32 { move |x| x + n}
// A closure that returns a running total: each call adds `step` and returns the total.// Every counter made by this function has its own total.fn make_counter(step: i32) -> impl FnMut() -> i32 { let mut total = 0; move || { total += step; total }}
// A closure that applies `first` to its argument and then `second` to the result.fn compose(first: impl Fn(i32) -> i32, second: impl Fn(i32) -> i32) -> impl Fn(i32) -> i32 { move |x| second(first(x))}
fn main() { let add2 = make_adder(2); let add10 = make_adder(10); assert_eq!(add2(1), 3); assert_eq!(add10(1), 11);
// Two counters made by the same function do not share a total, even when their // calls are interleaved. let mut by_one = make_counter(1); let mut by_five = make_counter(5); assert_eq!(by_one(), 1); assert_eq!(by_one(), 2); assert_eq!(by_five(), 5); assert_eq!(by_one(), 3); assert_eq!(by_five(), 10);
let add_then_double = compose(make_adder(1), |x| x * 2); let double_then_add = compose(|x| x * 2, make_adder(1)); assert_eq!(add_then_double(5), 12); assert_eq!(double_then_add(5), 11);
// A composed closure can be composed again. let add3 = compose(make_adder(1), make_adder(2)); let add_then_double_then_negate = compose(add_then_double, |x| -x); assert_eq!(add3(0), 3); assert_eq!(add_then_double_then_negate(5), -12); println!("all checks passed");}all checks passed
Fn, FnMut or FnOnce?
Three functions call a closure: once, several times, and through &self. The starter declares every parameter FnOnce, so it compiles. Write the bodies and pick the most permissive trait that works for each.
// Three functions that call a closure they are given. Every parameter below is// `FnOnce`, because `FnOnce` accepts every closure and so the starter compiles.// Write the body of each function, read the compiler's complaint, and change the trait// to the most permissive one that works: `FnOnce` if the function calls it once,// `FnMut` if it calls it several times, `Fn` if it calls it through a shared reference.
// Calls `job` once and returns what it gives back.fn run_once(job: impl FnOnce() -> String) -> String { todo!("call job once and return its result")}
// Calls `tick` `times` times.fn repeat(times: u32, tick: impl FnOnce()) { todo!("call tick `times` times")}
struct Button<F> { on_click: F,}
impl<F: FnOnce() -> i32> Button<F> { // Takes `&self`, so it can be called any number of times on a shared button. fn click(&self) -> i32 { todo!("call on_click through &self") }}
fn main() { // This closure hands `name` over, so it can only run once. let name = String::from("ferris"); assert_eq!(run_once(move || name), "ferris");
// This one changes `total`, so it runs many times but needs `mut` access. let mut total = 0; repeat(3, || total += 2); assert_eq!(total, 6); repeat(0, || total += 100); assert_eq!(total, 6);
// This one only reads `limit`, so a shared button can call it again and again. let limit = 10; let button = Button { on_click: || limit * 2, }; assert_eq!(button.click(), 20); assert_eq!(button.click(), 20); println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
// Three functions that call a closure they are given. Pick the trait each parameter// needs: `FnOnce` if the function calls it once, `FnMut` if it calls it several times,// `Fn` if it calls it through a shared reference (`&self`).
// Calls `job` once and returns what it gives back.fn run_once(job: impl FnOnce() -> String) -> String { job()}
// Calls `tick` `times` times.fn repeat(times: u32, mut tick: impl FnMut()) { for _ in 0..times { tick(); }}
struct Button<F> { on_click: F,}
impl<F: Fn() -> i32> Button<F> { // Takes `&self`, so it can be called any number of times on a shared button. fn click(&self) -> i32 { (self.on_click)() }}
fn main() { // This closure hands `name` over, so it can only run once. let name = String::from("ferris"); assert_eq!(run_once(move || name), "ferris");
// This one changes `total`, so it runs many times but needs `mut` access. let mut total = 0; repeat(3, || total += 2); assert_eq!(total, 6); repeat(0, || total += 100); assert_eq!(total, 6);
// This one only reads `limit`, so a shared button can call it again and again. let limit = 10; let button = Button { on_click: || limit * 2, }; assert_eq!(button.click(), 20); assert_eq!(button.click(), 20); println!("all checks passed");}all checks passed
A closure that uses a local
Write closest: retain the words that are not banned, then sort_by_key by how far each length is from target, ties alphabetical. Both closures use a local variable, as most closures you write will. The key may not borrow from the item.
// Most closures you write call a method that takes one: `retain`, `sort_by_key`.// Here both use a local variable from the function around them.
// Removes the words in `banned`, then sorts the rest by how far their length (in// bytes) is from `target`, nearest first. Words the same distance away stay in// alphabetical order.fn closest(words: &mut Vec<String>, target: usize, banned: &[&str]) { todo!("retain the words that are not banned, then sort_by_key with a key that uses `target`")}
fn main() { let mut words: Vec<String> = ["pear", "fig", "banana", "kiwi", "apple", "plum"] .iter() .map(|w| w.to_string()) .collect(); closest(&mut words, 4, &["apple"]); // distance 0: kiwi, pear, plum (alphabetical); distance 1: fig; distance 2: banana assert_eq!(words, ["kiwi", "pear", "plum", "fig", "banana"]);
// A different target changes the order; a banned word that is not there is fine. closest(&mut words, 6, &["grape"]); assert_eq!(words, ["banana", "kiwi", "pear", "plum", "fig"]);
// Nothing left, nothing to sort. let mut few = vec![String::from("fig")]; closest(&mut few, 3, &["fig"]); assert!(few.is_empty()); println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
// Most closures you write call a method that takes one: `retain`, `sort_by_key`.// Here both use a local variable from the function around them.
// Removes the words in `banned`, then sorts the rest by how far their length (in// bytes) is from `target`, nearest first. Words the same distance away stay in// alphabetical order.fn closest(words: &mut Vec<String>, target: usize, banned: &[&str]) { words.retain(|w| !banned.contains(&w.as_str())); words.sort_by_key(|w| (w.len().abs_diff(target), w.clone()));}
fn main() { let mut words: Vec<String> = ["pear", "fig", "banana", "kiwi", "apple", "plum"] .iter() .map(|w| w.to_string()) .collect(); closest(&mut words, 4, &["apple"]); // distance 0: kiwi, pear, plum (alphabetical); distance 1: fig; distance 2: banana assert_eq!(words, ["kiwi", "pear", "plum", "fig", "banana"]);
// A different target changes the order; a banned word that is not there is fine. closest(&mut words, 6, &["grape"]); assert_eq!(words, ["banana", "kiwi", "pear", "plum", "fig"]);
// Nothing left, nothing to sort. let mut few = vec![String::from("fig")]; closest(&mut few, 3, &["fig"]); assert!(few.is_empty()); println!("all checks passed");}all checks passed
A registry of callbacks
Store closures of different types in one Vec<Box<dyn Fn>>, then call those registered under a name, in order. The checks include an unknown name and names that share a prefix with a registered one: only an exact name matches.
// A registry of named callbacks. `on` stores a handler under a name; `fire` calls every// handler stored under that name, in the order they were added, and collects what they// return. A name with no handlers gives an empty Vec.struct Registry { handlers: Vec<(String, Box<dyn Fn(&str) -> String>)>,}
impl Registry { fn new() -> Registry { Registry { handlers: Vec::new(), } }
// `'static` means the closure may not borrow local variables: it must own what it uses. fn on(&mut self, name: &str, handler: impl Fn(&str) -> String + 'static) { todo!("box the handler and push it with its name") }
fn fire(&self, name: &str, arg: &str) -> Vec<String> { todo!("call each handler whose name matches, in order") }}
fn main() { let mut registry = Registry::new(); let prefix = String::from("hello"); registry.on("greet", move |who| format!("{prefix}, {who}")); registry.on("greet", |who| who.to_uppercase()); registry.on("count", |text| text.len().to_string()); registry.on("greeting", |_| String::from("not a greet")); registry.on("", |_| String::from("empty name"));
assert_eq!(registry.fire("greet", "ana"), ["hello, ana", "ANA"]); assert_eq!(registry.fire("count", "hello"), ["5"]); assert!(registry.fire("missing", "x").is_empty()); // Only an exact name matches: "greet" is a prefix of "greeting", "count" is not. assert_eq!(registry.fire("greeting", "x"), ["not a greet"]); assert_eq!(registry.fire("gree", "x"), Vec::<String>::new()); assert_eq!(registry.fire("", "x"), ["empty name"]);
let empty = Registry::new(); assert!(empty.fire("greet", "x").is_empty()); println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
// A registry of named callbacks. `on` stores a handler under a name; `fire` calls every// handler stored under that name, in the order they were added, and collects what they// return. A name with no handlers gives an empty Vec.struct Registry { handlers: Vec<(String, Box<dyn Fn(&str) -> String>)>,}
impl Registry { fn new() -> Registry { Registry { handlers: Vec::new(), } }
// `'static` means the closure may not borrow local variables: it must own what it uses. fn on(&mut self, name: &str, handler: impl Fn(&str) -> String + 'static) { self.handlers.push((name.to_string(), Box::new(handler))); }
fn fire(&self, name: &str, arg: &str) -> Vec<String> { let mut results = Vec::new(); for (registered, handler) in &self.handlers { if registered == name { results.push(handler(arg)); } } results }}
fn main() { let mut registry = Registry::new(); let prefix = String::from("hello"); registry.on("greet", move |who| format!("{prefix}, {who}")); registry.on("greet", |who| who.to_uppercase()); registry.on("count", |text| text.len().to_string()); registry.on("greeting", |_| String::from("not a greet")); registry.on("", |_| String::from("empty name"));
assert_eq!(registry.fire("greet", "ana"), ["hello, ana", "ANA"]); assert_eq!(registry.fire("count", "hello"), ["5"]); assert!(registry.fire("missing", "x").is_empty()); // Only an exact name matches: "greet" is a prefix of "greeting", "count" is not. assert_eq!(registry.fire("greeting", "x"), ["not a greet"]); assert_eq!(registry.fire("gree", "x"), Vec::<String>::new()); assert_eq!(registry.fire("", "x"), ["empty name"]);
let empty = Registry::new(); assert!(empty.fire("greet", "x").is_empty()); println!("all checks passed");}all checks passed
Read more
- The Rust Book: Closures, including how the compiler picks
Fn,FnMutandFnOnce. - Rust by Example: Closures.
- std docs:
Option::unwrap_or_else,slice::sort_by_key,Fn,FnMut,FnOnceand function pointers. - The Rust Reference: closure types, the exact capture rules.
- Go: Fixing For Loops in Go 1.22. Python: the FAQ entry on lambdas defined in a loop. C++: lambda expressions. C#: lambda expressions.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.