Rust Disjoint Captures
Disjoint captures let a closure capture only the places it touches. A place is one field or one tuple item of a value.
One field is not the whole struct
Form is a struct with two String fields, name and city. This closure touches only form.city, so while it lives, form.name stays free — the program reads it between defining the closure and calling it:
let mut form = Form { name: String::from("Sam"), city: String::from("Seattle") };
let mut fix_city = || form.city.push_str(", WA");
println!("name = {}", form.name);
fix_city();
println!("city = {}", form.city);Tuples split the same way
A closure holding pair.0 hostage leaves pair.1 completely alone:
let mut pair = (String::from("a"), String::from("b"));
let mut fix0 = || pair.0.push('!');
println!("other = {}", pair.1);
fix0();
println!("fixed = {}", pair.0);Arrays refuse to split
An index is a run-time value, so the capture cannot be precise. This closure takes the whole array as its &mut borrow. Reading arr[1] while it lives is refused:
let mut arr = [String::from("a"), String::from("b")];
let mut fix0 = || arr[0].push('!');
println!("{}", arr[1]); // error E0502: arr is borrowed as mutable, wholeWhen a split is refused, borrow the field first
Inside a method this is the everyday pattern. Iterating self.set while a closure pushes into self.vec looks like one borrow too many. The fix is to borrow the field into a local first. Then the closure captures the local instead of self:
let vec = &mut self.vec;
self.set.iter().for_each(|&n| vec.push(n));Example
struct Page {
heading: String,
text: String,
}
fn main() {
let mut page = Page {
heading: String::from("Intro"),
text: String::from("draft"),
};
// Two closures alive at once, each holding a different field.
let mut fix_heading = || page.heading.push('!');
let show_text = || println!("text = {}", page.text);
fix_heading();
show_text();
println!("heading = {}", page.heading);
// A field can move out while a closure keeps a sibling field.
let pair = (String::from("left"), String::from("right"));
let show_right = || println!("right = {}", pair.1);
let left = pair.0;
show_right();
println!("left = {left}");
}Key points
- A capture names a place: a variable, a field of it, or a field inside a field.
- The compiler follows the exact path your body uses
- Two closures may hold disjoint places of one value at once: one may read form.name while another mutates form.city
- A field captured by value drops when the closure drops.
- Its sibling fields drop later, together with the original value
- Splitting stops at a reference.
- A closure reaching through an & or an Rc captures the reference itself, not the pieces behind it.
- Box is the one exception, treated as part of the value
- Arrays and slices never split.
- Using arr[0] captures the whole array.
- An index is only worked out at run time
- The body may also use the whole variable somewhere — printing it, or passing it on.
- Then the whole variable is captured.
- The capture uses the strongest strength in the body
- Edition 2021 introduced this finer capture.
- Disjoint captures hold only the touched fields or items, not the whole value.
In this example, two closures are alive at the same time over one Page. fix_heading captures page.heading by &mut borrow. show_text captures page.text by shared borrow. The fields are disjoint, so the borrows never overlap: the text prints as draft, and afterwards the heading reads Intro!. Below, show_right captures only pair.1, so pair.0 is still free to move into left. The closure prints right = right, and main prints its own left = left. Before edition 2021, both halves of this program failed: a closure took the whole variable or nothing.
Note
The older fix still works in every edition.
Bind the field to a local name first (let vec = &mut self.vec).
Then let the closure capture the local.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
In a 2021 crate, one live closure mutates page.heading while another reads page.text. Why does this compile?