Closure capture
Closures and async blocks can refer to the places in their environment:
#![allow(unused)] fn main() { let mut x = 0; let mut increment = || x += 1; // `&mut x` is captured increment(); increment(); assert_eq!(x, 2); }
This gets eventually compiled to a data type that stores references to, or the contents of, the captured places. The compiler determines automatically how to capture each place depending on how it is used in the closure/async block.
In this step, we use move expressions and let place to make all these captures explicit (note that this is very
different from move!(..) introduced in Explicit Copies/Moves).
For every captured place, we introduce at the start of the closure a let place p = ...; that uses
a move expression.
The rest of the closure stays unchanged.
Our initial example becomes:
#![allow(unused)] fn main() { let mut increment = || x = copy!(x) + 1; // desugars to: let mut increment = || { let place x = *move(&mut x); x = copy!(x) + 1 }; }
Another example:
#![allow(unused)] fn main() { let mut x = Some(42); let mut replace = move |new: u32| Option::replace(&mut x, copy!(new)); // desugars to: let mut replace = |new: u32| { let place x = move(x); Option::replace(&mut x, copy!(new)) }; }
This final example uses a unique immutable borrow (which we introduce in Unique-Immutable
Borrow) since a &mut borrow would require let mut rx:
#![allow(unused)] fn main() { let mut x = 42; let rx = &mut x; let mut increment = || *rx += 1; // desugars to: let mut increment = || { let place rx = *move(&uniq rx); *rx += 1 }; }
See the Reference for details about what gets captured and how. Thanks to previous desugarings all place uses are explicit, which makes the analysis straightforward.
After this step, all closure and async block captures are explicit, using move expressions.
Backlinks