RustArena

Step 2 of 6

Capturing variables from the scope

Here is what makes closures more than short functions: a closure can use the variables of the scope it's written in. The closure captures them.

fn main() {
    let tax_rate = 0.25;
    let with_tax = |price: f64| price * (1.0 + tax_rate);

    println!("{:.2}", with_tax(8.0));
    println!("{:.2}", with_tax(20.0));
    println!("rate is still {tax_rate}");
}
10.00
25.00
rate is still 0.25

with_tax uses tax_rate without taking it as a parameter. A fn can't do that, even one written inside main:

fn main() {
    let tax_rate = 0.25;
    fn with_tax(price: f64) -> f64 {
        price * (1.0 + tax_rate)
    }
    println!("{:.2}", with_tax(8.0));
}
error[E0434]: can't capture dynamic environment in a fn item

The compiler even suggests the fix: help: use the `|| { ... }` closure form instead.

Three ways to capture

The compiler looks at what the body does with each variable and captures it in the gentlest way that works:

The body... The closure captures Like writing
only reads x a shared borrow &x
changes x a mutable borrow &mut x
takes ownership of x (moves it somewhere) the value itself a move of x

All the borrowing rules you know apply, with the closure as the borrower. A closure that changes a captured variable must itself be declared mut, because calling it changes something:

fn main() {
    let mut count = 0;
    let mut tick = || count += 1;

    tick();
    tick();
    tick();
    println!("ticks: {count}");
}
ticks: 3

The mutable borrow of count starts where tick is created and lasts until the last call of tick. After that, count is free again, so the println! is fine.

Common mistakes

fn main() {
    let mut count = 0;
    let tick = || count += 1;
    tick();
    println!("{count}");
}
error[E0596]: cannot borrow `tick` as mutable, as it is not declared as mutable
fn main() {
    let mut count = 0;
    let mut tick = || count += 1;
    tick();
    println!("{count}");
    tick();
}
error[E0502]: cannot borrow `count` as immutable because it is also borrowed as mutable

Fix it by finishing with the closure first, or by passing the value in as a parameter instead of capturing it.

Your task

The program has a bonus and a score. Write a closure add_points that takes a number of points (i32) and adds points + bonus to score. Call it with 10 and then with 20, so the program prints score: 40.

Challenge 2

A closure can capture a Vec mutably too. Write a closure log that takes a message (&str) and pushes a numbered line onto lines: the first message becomes "1. server started", the second "2. ...", and so on (the number is lines.len() + 1). Log the three messages server started, user ann logged in and disk 91% full, in that order. The loop at the end prints them:

1. server started
2. user ann logged in
3. disk 91% full

Challenge 3

The starter code doesn't compile: the first println! reads visits while the closure visit still borrows it mutably (it's called again later). Fix it by reordering lines, not by adding .clone(), so the program prints:

visited: ["hall", "kitchen", "garden"]
count: 3

Previous: Your first closure Next: move closures