Expand description
Provides two macros for more powerful closure captures.
§Background
Closures in Rust, despite being extremely powerful, do not offer many options for modifying the
way in which they capture their output. A particular pain point is often needing to .clone()
an Arc<T> or Rc<T> for the closure to capture. This pattern does not compile:
fn needs_static<T: FnOnce() -> i32 + 'static>(f: T) -> i32 {
f()
}
let local: Rc<i32> = Rc::new(1);
// Try and capture a clone of the `Rc`
let mut f = || {
let in_closure = local.clone();
*in_closure.as_ref()
};
// `f` is not `'static`!
assert_eq!(needs_static(f), 1);
// `local` has not been captured!
assert_eq!(*local.as_ref(), 1);That’s because when writing local.clone() in the body of the closure, that clone call is not
executed until the closure is called; this means that the closure is actually capturing a
&local and so it’s not 'static! Making f a move closure does not fix this, since then
local will be captured by value, and the later local.as_ref() statement will fail. What we
want instead is for the .clone() to be executed when the closure is created:
fn needs_static<T: FnOnce() -> i32 + 'static>(f: T) -> i32 {
f()
}
let local: Rc<i32> = Rc::new(1);
// Actually capture a clone of the `Rc`
let cloned = local.clone();
let f = move || {
let in_closure = cloned;
*in_closure.as_ref()
};
// `f` is now `'static`!
assert_eq!(needs_static(f), 1);
// `local` has not been captured!
assert_eq!(*local.as_ref(), 1);§Usage
The captures::capture and captures::capture_only macros are invoked with a comma-seperated
list of “capture directives” and finally a closure expression. One example of a capture
directive is the clone x directive, which indicates that a clone of x should be captured in
place of x. As such, the example above can be re-written to:
use captures::capture;
fn needs_static<T: FnOnce() -> i32 + 'static>(f: T) -> i32 {
f()
}
let local: Rc<i32> = Rc::new(1);
// Actually capture a clone of the `Rc`
let f = capture!(clone local,
move || {
let in_closure = local;
*in_closure.as_ref()
}
);
// `f` is still `'static`!
assert_eq!(needs_static(f), 1);
// `local` has not been captured!
assert_eq!(*local.as_ref(), 1);§Capture Directives
These capture directives are currently supported:
clone xcaptures a clone ofx.with x = exprcaptures a valuexthat is computed fromexpr.all xcaptures all ofx. Beginning in Rust 2021, writingx.yin your closure would lead to only theyfield ofxbeing captured. Specifyingall xcauses all ofxto be captured instead. This does not influence whetherxis captured by value or by reference - if the closure is amoveclosure, it will still be captured by value, and if it is a non-moveclosure, the compiler’s standard inference algorithm is allowed to make the decision.
To avoid surprises and compilation errors, if you specify a clone or with directive, then
this macro will turn your closure into a move closure if it was not one already. Because of
this, if your closure is a move closure - either because you explicitly marked it as such or
because you used a with or clone directive - then you may additionally specify these
directives:
ref xcapturesxby immutable reference.ref mut xcapturesxby mutable reference.
The x in all of these directives must simply be the name of a local variable. Some more
complicated things may be supported in the future. There is at the moment also no support for
combining directives. I will add this once I figure out a pretty and consistent way to do it.
§Mutability
In Rust, captured variables that are captured by value inherit the mutability of the value they reference. For example,
let a = 1; // immutable
let _ = move || {
a += 1;
a
};does not compile, but if a is marked as mutable
let mut a = 1; // now mutable
let _ = move || {
a += 1;
a
};it does.
Unfortunately, this crate does not have the necessary information to reproduce this behavior in
general. clone and with directives create new variables for which it is not clear what their
mutability should be. The current policy is for all of them to default to immutable. This may be
changed in the future (obviously respecting semver) if it is determined that this is not the
best option. If you do want these values to be mutable, you can request that by prefixing the
variable with a mut. For example,
let mut v = vec![1, 2]; // despite being mutable here
let _ = capture!(clone v, || {
v.push(3); // we cannot push to `v`, since it is not mutable
v
});We can fix this via:
let mut v = vec![1, 2];
let _ = capture!(clone mut v, || {
v.push(3); // we can push now
v
});This will still emit a warning because the mutability of the variable v outside the closure is
unused. Writing instead let v = vec![1, 2]; would continue to compile and the warning would not
be emitted.
all directives are not affected by this. Variables captured under such a directive, if
captured by value, correctly inherit their mutability. As such, the mut prefix is not
supported on these directives.
§capture_only
The capture_only macro behaves exactly like the capture macro, with the exception that it
additionally prevents any variables that do not have an associated capture directive from
being captured. For example,
let a = 1;
let mut b = 10;
let mut f = capture_only!(all a, || {
b += 1; // error
a + 1
});
assert_eq!(f(), 2);
assert_eq!(b, 11);does not compile, with an error message indicating that there is no local variable b.
Switching capture_only to capture would allow the above code to compile. If you would like
to indicate that b may also be captured, but do not want to add any restrictions on how, you
can add an all directive:
let a = 1;
let mut b = 10;
let mut f = capture_only!(all a, all b, || {
b += 1; // compiles
a + 1
});
assert_eq!(f(), 2);
assert_eq!(b, 11);Macros§
- capture
- Allows specifying how a closure should capture variables.
- capture_
only - Allows specifying how a closure should capture variables.