mirror of
https://github.com/leonardomso/rust-skills.git
synced 2026-09-14 19:33:21 +08:00
2.9 KiB
2.9 KiB
closure-move-capture
Use
movefor closures that outlive the current scope; clone beforemoveto keep the original
Why It Matters
A closure that borrows its environment can only live as long as that environment. When a closure escapes — passed to a new thread, stored in a struct, or returned from a function — it must own its captures and typically must be 'static. The move keyword transfers ownership of every captured variable into the closure. If you need the value in both the closure and the surrounding code, clone it first and move the clone.
Bad
fn make_greeter_bad(name: String) -> impl Fn() {
// `name` is borrowed, but the closure outlives the function frame —
// the compiler rejects this with a lifetime error.
// || println!("hello, {name}") // error: `name` does not live long enough
move || println!("hello, {name}") // must be move — shown here to illustrate the fix
}
fn spawn_bad() {
let data = vec![1, 2, 3];
// Borrowing `data` across a thread boundary is rejected:
// std::thread::spawn(|| println!("{data:?}")); // error: borrowed value does not live long enough
let _ = data; // suppress unused warning
}
Good
fn process(data: &[i32]) -> i32 {
data.iter().sum()
}
// Return a closure that owns its capture via `move`.
fn make_greeter(name: String) -> impl Fn() {
move || println!("hello, {name}")
}
// Clone before `move` when you need the value in both places.
fn spawn_and_keep(data: Vec<i32>) -> std::thread::JoinHandle<i32> {
let data_for_thread = data.clone(); // clone goes into the closure
let handle = std::thread::spawn(move || process(&data_for_thread));
// `data` is still available here
println!("original still owned: {data:?}");
handle
}
fn demo() {
let greet = make_greeter(String::from("world"));
greet(); // prints: hello, world
let nums = vec![10, 20, 30];
let handle = spawn_and_keep(nums);
let sum = handle.join().unwrap();
assert_eq!(sum, 60);
}
Key Points
- Thread closures passed to
std::thread::spawnmust be'static, whichmoveenables when all captured types are'static + Send. - Clone selectively: clone only what the closure needs, not the whole owning struct — see closure-disjoint-capture.
- Async tasks follow the same rule:
tokio::spawn(async move { … })takes ownership; clone shared data before theasync moveblock. - A
moveclosure can still implementFnorFnMut—moveonly controls how captures are taken, not how many times the closure can be called.
See Also
- async-clone-before-await - clone data before async move blocks
- own-move-large - move large data instead of cloning
- closure-disjoint-capture - capture only the fields you use