12.5 Advanced Topics
Finally, let’s briefly touch upon a few more advanced aspects of using closures.
12.5.1 Returning Closures
Since each closure has a unique, unnameable type, functions must return them opaquely:
-
impl Trait
: Preferred. Returns an opaque type implementing the trait(s). Enables static dispatch.#![allow(unused)] fn main() { fn make_adder(a: i32) -> impl Fn(i32) -> i32 { move |b| a + b // Returns a specific, unnamed closure type } }
-
Box<dyn Trait>
: Returns a trait object on the heap. Requires heap allocation and dynamic dispatch, but allows returning different closure types.#![allow(unused)] fn main() { fn make_adder_boxed(a: i32) -> Box<dyn Fn(i32) -> i32> { Box::new(move |b| a + b) } }
12.5.2 Disjoint Capture in Closures (Rust 2021+)
Starting with the Rust 2021 Edition, closures capture struct fields more precisely using a feature called disjoint capture. Instead of capturing the entire struct variable, a closure now typically captures only the specific fields it actually uses.
When a closure uses a field whose type is not Copy
(like String
), disjoint capture means only that specific field is moved into the closure, transferring its ownership.
The primary effect is that the specific moved field becomes temporarily inaccessible via the original variable. While this field is “moved out”, operations requiring the whole struct to be valid (like moving it or using default Debug
formatting) are also temporarily disallowed.
However, Rust tracks the validity of each field individually. Since other fields were not captured, they remain accessible:
- You can read (copy) remaining
Copy
fields (likeu32
). - You can immutably borrow remaining non-
Copy
fields (likeString
). - If the struct variable is mutable, you can assign new values to these other fields, or even re-assign a value to the originally moved field, making the struct whole and fully usable again.
#[derive(Debug)] // For final print struct Settings { mode: String, // Not Copy api_key: String, // Not Copy retries: u32, // Copy } fn main() { let mut settings = Settings { // Must be mutable for re-assignment mode: "fast".to_string(), api_key: "ABC-123".to_string(), retries: 3 }; // Closure moves settings.mode due to disjoint capture (Rust 2021+) let mode_closure = move || { println!("Mode is: {}", settings.mode); }; mode_closure(); // settings.mode is now moved out // Other fields remain accessible: println!("API Key: {}", settings.api_key); // OK (Immutable borrow) println!("Retries: {}", settings.retries); // OK (Copy) // Cannot access moved field or use struct as whole yet: // println!("{}", settings.mode); // Error: use of moved value // println!("{:?}", settings); // Error: requires all fields // Can re-assign the moved field, making the struct whole again: settings.mode = "slow".to_string(); // Now all fields and the struct are fully usable: println!("Mode after re-assignment: {}", settings.mode); // OK println!("Full settings: {:?}", settings); // OK }
Disjoint capture makes closures more ergonomic and efficient, allowing finer-grained ownership transfer from structs. (Prior to the Rust 2021 edition, move
closures would capture the entire settings
struct if they used even one field like settings.mode
, preventing subsequent access like println!("API Key: {}", settings.api_key);
.)