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
move |points: u32| ...movesplayer(aString) into the closure. The closure owns it now. The move happens once, when the closure is created, so you can callannounceas often as you like.limitis ani32, a Copy type, somovecopies it into the closure. The original stays usable.
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?
- The closure must outlive the current function: returning it, storing it, spawning a thread. The compiler tells you (you'll see the error in the step on returning closures).
- You want the closure to own its data so nothing else can change it meanwhile.
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