RustArena

Step 3 of 6

move closures

Sometimes borrowing isn't enough: the closure has to own what it uses, because it will outlive the variables around it. That happens when you return a closure from a function (two steps from now), keep it in a struct for later, or hand it to another thread (in the threads module). The move keyword before the pipes makes the closure take ownership of everything it captures.

fn main() {
    let player = String::from("Ferris");
    let announce = move |points: u32| println!("{player} scores {points}!");
    announce(10);
    announce(25);

    let limit = 100;
    let within_limit = move |x: i32| x <= limit;
    println!("{} {}", within_limit(42), within_limit(512));
    println!("limit is still usable: {limit}");
}
Ferris scores 10!
Ferris scores 25!
true false
limit is still usable: 100

After a move, the original is gone

fn main() {
    let player = String::from("Ferris");
    let announce = move || println!("{player} is ready");
    announce();
    println!("{player}");
}
error[E0382]: borrow of moved value: `player`

When both the closure and the rest of the code need the data, clone it before creating the closure and move the clone in:

fn main() {
    let player = String::from("Ferris");
    let player_for_closure = player.clone();
    let announce = move || println!("{player_for_closure} is ready");
    announce();
    println!("{player} is still here");
}
Ferris is ready
Ferris is still here

move and Copy values: a surprise

With move, a closure that changes a captured Copy value changes its own copy:

fn main() {
    let mut count = 0;
    let mut next_id = move || {
        count += 1;
        count
    };
    println!("{} {} {}", next_id(), next_id(), next_id());
    println!("outer count: {count}");
}
1 2 3
outer count: 0

That's handy (the closure carries a private counter around with it) but surprising if you expected count to change. Without move, the closure would borrow count mutably, and after the last call the outer count would be 3.

When do you need move?

Otherwise, leave move out and let the compiler borrow.

Your task

Write a move closure label that takes a message (&str) and returns a String made of tag, a space, and the message. Print label("disk almost full") and label("cpu hot"), so the program prints:

[warn] disk almost full
[warn] cpu hot

Challenge 2

The starter doesn't compile: city is moved into the forecast closure, so the last line can't use it. Keep the closure a move closure, and fix the program by giving the closure its own clone of city. It should print:

Lisbon: 21 C
Lisbon: 24 C
city: Lisbon

Challenge 3

A move closure can own a whole collection and change it. The starter has a queue of customers. Write a move closure serve that removes and returns the first name in the queue as Some(name), or None when the queue is empty. Then use while let to print now serving <name> for each customer, and finally queue empty:

now serving Ann
now serving Bo
now serving Cy
queue empty

Previous: Capturing variables from the scope Next: Fn, FnMut and FnOnce