Rust infers how to capture v , and because println! only needs a reference to v , the closure tries to borrow v . However, there’s a problem: Rust can’t tell how long the spawned thread will run, so it doesn’t know if the reference to v will always be valid.
In your example, for value in v doesn't only need a reference -- it's needs ownership of v. Thus Rust infers that it needs to capture v (and not just a reference to v) from the start, and you don't need the move.
(Note that they used println! on v itself, unlike your code inside the loop.)
The fact that for v in value takes ownership is exactly why move is not needed. The closure doesn't make sense unless v is captured (moved), and so it is -- even without the move keyword.
struct MyNonCopyStruct;
impl MyNonCopyStruct {
fn takes_ownership(self) {}
fn only_needs_borrow(&self) {}
}
fn main() {
let v = MyNonCopyStruct;
let w = MyNonCopyStruct;
// OK without `move` because taking ownership implies `v` _must_ be captured (moved)
let _ = std::thread::spawn(|| v.takes_ownership());
// Error because only a reference to `w` is captured and it may not live long enough
// let _ = std::thread::spawn(|| w.only_needs_borrow());
// Fixed if we _explicitly_ move even though only a reference is used
let _ = std::thread::spawn(move || w.only_needs_borrow());
}