What are move closures (move || { ... })? When are they necessary, and how do they interact with ownership?
Table of Contents
A move
closure (defined with the move
keyword) forces the closure to take ownership of variables it captures from the environment. Unlike regular closures, which capture variables by reference (immutable or mutable) when possible, move
closures move or copy the variables into the closure itself.
Key Mechanics
1. Ownership Transfer
For non-Copy types (e.g.,
String
,Vec
), the closure takes ownership of the variable:let s = String::from("hello"); let closure = move || println!("{}", s); // `s` is moved into the closure // println!("{}", s); // ERROR: `s` was moved
For Copy types (e.g.,
i32
,bool
), the closure copies the value:let x = 42; let closure = move || println!("{}", x); // `x` is copied println!("{}", x); // OK: `x` is still valid
2. Interaction with Closure Traits
A move
closure’s trait (Fn
, FnMut
, FnOnce
) depends on how the captured variables are used:
Fn
: Read-only access to captured variables.FnMut
: Mutates captured variables.FnOnce
: Consumes captured variables (e.g.,drop
).
When Are Move Closures Necessary?
1. Closures Outliving Their Environment
When a closure is used in a different scope (e.g., a thread or async task), it must own its data to avoid dangling references:
use std::thread;
let data = String::from("thread-safe");
thread::spawn(move || { // `move` forces ownership of `data`
println!("{}", data); // Safe: `data` lives in the closure
}).join().unwrap();
2. Breaking Reference Cycles
If a closure needs to capture a value that’s also borrowed elsewhere, move
ensures ownership is transferred:
let mut vec = vec![1, 2, 3];
let closure = move || { // Takes ownership of `vec`
// vec.push(4); // ERROR: `vec` is moved (can’t mutate)
};
// vec.push(4); // ERROR: `vec` is moved into closure
3. Explicit Ownership Control
When you want to avoid accidental borrows or force a copy:
let x = 42;
let closure = || println!("{}", x); // Borrows `x`
let move_closure = move || println!("{}", x); // Copies `x` (since `i32` is `Copy`)
Examples
1. Non-Copy Type (Ownership Moved)
let s = String::from("hello");
let closure = move || println!("{}", s);
closure(); // Works: closure owns `s`
// closure(); // ERROR if `s` is consumed (e.g., `FnOnce`)
2. Copy Type (Value Copied)
let x = 42;
let closure = move || x + 1; // Copies `x`
println!("{}", x); // OK: `x` is `Copy`
3. Mixing move
and Mutation
let mut count = 0;
let mut closure = move || { // `count` is copied (since `i32` is `Copy`)
count += 1; // Operates on the copied `count`
count
};
println!("{}", closure()); // 1
println!("{}", closure()); // 2
println!("{}", count); // 0 (original unchanged)
Pitfalls
Unintended Moves:
let s = String::from("hello"); let _ = move || println!("{}", s); // `s` moved here // println!("{}", s); // ERROR: `s` is gone
Overusing
move
: Unnecessary copies/moves can hurt performance or cause compile errors.
Key Takeaways
✅ Use move
closures when:
- The closure outlives its environment (e.g., threads).
- You need explicit ownership to avoid borrow checker issues.
✅ Avoid move
for:
- Local, short-lived closures that don’t escape their scope.
Copy
types where borrowing is sufficient.
Try This: What happens if you use move
with a closure that captures a mutable reference (&mut T
)?
Answer: The reference itself is moved (but the data it points to isn’t owned). This is rarely useful and may lead to lifetime errors!