Skip to content

Q036. How do closures capture ownership, and what does move mean for a closure?

Short Answer

Closures capture only the environment places they need, by shared borrow, mutable borrow, or value, as inferred from use. A move closure forces capture by value, transferring non-Copy captures and copying Copy captures into the closure. move does not automatically make a closure FnOnce; the body determines whether captured values are moved out when called.

Deep Dive

A closure is an anonymous compiler-generated struct whose fields represent captures. Capture mode is chosen independently from call behavior:

  • reading can capture by &T;
  • mutation can capture by &mut T;
  • consuming a value requires capture by value;
  • move requests by-value capture even when the body only reads.

The closure traits depend on what invocation does with captures. A closure that only reads owned captures can implement Fn and run repeatedly. Mutating captures requires FnMut. Moving a captured non-Copy value out of the closure requires FnOnce.

By-value capture is useful when the closure must outlive the current stack frame, such as a spawned thread or many async tasks. Required Send and lifetime bounds are separate checks.

Internal Model

rustc creates a closure type containing captured places, often using disjoint field capture. A move closure stores owned values or copied Copy values in those fields. Dropping the closure drops owned captures, although the relative drop order of by-value captures is unspecified.

Closure calls lower through Fn, FnMut, or FnOnce receiver methods using &self, &mut self, or self respectively.

Example

rust
fn main() {
    let message = String::from("hello");

    let print = move || println!("{message}");

    print();
    print(); // allowed: the body only borrows its owned capture

    // println!("{message}"); // error: message moved into the closure
}

Common Mistakes

  • Saying move means every captured value is physically deep-copied.
  • Assuming every move closure implements only FnOnce.
  • Confusing capture mode with Send, Sync, or 'static requirements.

Follow-ups

  • What makes a closure implement Fn, FnMut, or FnOnce?
  • How does disjoint field capture affect partial moves?
  • Why do thread and async APIs frequently require move closures?

References

  • The Rust Programming Language
  • Rust Reference
  • Rust Edition Guide