move Closures
move closures capture variables by value into the closure environment, required for thread::spawn and long-lived callbacks.
Recipe
use std::thread;
fn main() {
let data = vec![1, 2, 3];
thread::spawn(move || println!("{:?}", data));
}When to reach for this: Threads, tokio::spawn, timers, and when closure may outlive stack borrows.
Working Example
fn spawn_sum(nums: Vec<i32>) -> std::thread::JoinHandle<i32> {
std::thread::spawn(move || nums.iter().sum())
}
fn main() {
let h = spawn_sum(vec![1, 2, 3]);
println!("{}", h.join().unwrap());
}What this demonstrates:
numsmoved into closure- Main thread cannot use
numsafter spawn JoinHandlereturns result from thread
Deep Dive
move applies to all captured variables, not selective. Use clone before move if both thread and main need data.
Gotchas
- move when borrow would work - Extra clones. Fix: Omit
movefor short-lived closures. - move copies
Copytypes - Still "move" semantics but bits copied. - Arc needed for shared ownership - Multiple threads need
Arcclone before move. - RefCell across threads - Not
Send. Fix:Mutex. - async move block - Captures owned for future.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Arc + move | Shared read across threads | Single owner |
| Scoped threads | Borrow stack data | Detached thread |
| Channel send | Hand off ownership | Shared state needed |
FAQs
move all captures?
Yes - keyword applies to inferred capture set.tokio spawn?
Same move + Send + 'static.clone before move?
Keep copy in parent thread.FnOnce after move?
Consuming captures often FnOnce.copy types?
Copied into closure environment.ref in move?
Still moves owned refs (e.g. String).scoped threads?
May borrow without move - scoped API.closure size?
Large captured struct increases closure size.wasm?
thread spawn limited - move still for callbacks.lint?
redundant_clone around move patterns.Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.