Move Expressions for Closure Captures
Closures and async blocks can “capture” places from their surrounding environment, which is an implicit operation. To make this explicit I propose to use a feature called “move expressions” that’s been discussed lately.
This feature adds a move($expr) expression, valid in a closure, that:
- Evaluates
$exprin the parent of the closure; - Stores the result inside the closure;
- Acts as a place alias for the place where that result is stored (i.e. the field of the closure object).
For example, a function that increments a captured variable can be expressed as:
#![allow(unused)]
fn main() {
// Implicit capture:
let mut increment = || x += 1;
// Explicit capture:
let mut increment = || *move(&mut x) += 1;
}
To see why it’s important that move(..) be a place expression, consider:
#![allow(unused)]
fn main() {
let mut x = Some(42);
// This moves the whole of `x` inside the closure; this closure could be returned from
// the current function.
let mut replace = move |new: u32| x.replace(new);
// Explicit capture:
let mut replace = |new: u32| Option::replace(&mut move(x), new);
}
Here the &mut move(..) directly borrows the place where we stored the initial value, and modifies
it on each call. That would not work if move(..) was a value expression.
Move expressions can be nested: move(move($expr)) evaluates the inner move($expr) when the outer
closure is created, then the outer move(move($expr)) when the inner closure is created:
#![allow(unused)]
fn main() {
let mut x = Some(42);
let generate_replacer = || {
do_some_stuff();
|new: u32| Option::replace(&mut move(move(x)), new)
};
// is equivalent to:
let generate_replacer = || {
do_some_stuff();
let inner_x = move(x);
|new: u32| Option::replace(&mut move(inner_x), new)
};
}
Backlinks