capture

Macro capture 

Source
macro_rules! capture {
    ([$($args:tt)*], $($closure:tt)*) => { ... };
    ([$($args:tt)*]..$($closure:tt)*) => { ... };
}
Expand description

Generate a closure that captures specified variables, and captures all other unspecified variables by move.

The first argument to the macro is always a list of capture arguments wrapped in [], and you pass a closure or async block to the second argument, separated by commas. In this case, the second argument must be explicitly specified as move capture.

§Usage

§capture by copy

If you specify a variable name in square brackets, the Clone::clone function is called against that variable, creating a variable of the same name and capturing it with the specified closure. (Capturing the variable is always guaranteed, even if you don’t explicitly use it within the block.)

let a = 1;
let closure = capture_it::capture!([a], move || { a });
assert_eq!(closure(), 1);

All of these rules, except for reference capture (the & prefix), which we’ll discuss later, can be declared mutable by prefixing the variable name with * (an asterisk).

(NOTE: We originally wanted to use the mut keyword prefix, but that would cause rustfmt to consider the macro expression as unjustified rust code and disable formatting, so we were forced to adopt this unorthodox method)

let count = 0;
let mut closure = capture_it::capture!([*count], move || { count += 1; count });

assert_eq!(closure(), 1);
assert_eq!(closure(), 2);
assert_eq!(closure(), 3);

assert_eq!(count, 0); // as it was copied ...

§capture by reference

You can explicitly reference-capture a variable by prefixing its name with & or &mut. (Note that all variables not specified in the capture list will be MOVE-captured, as only blocks with a MOVE policy are allowed as second arguments. Any variables you want to capture by reference must be explicitly specified in the capture list)

let a = std::cell::Cell::new(1);
let closure = capture_it::capture!([&a], move || { a.get() });
a.set(2);
assert_eq!(closure(), 2);

§capture by alias

Similar to the lambda capture rules in modern C++, it is possible to capture an expression by giving it an alias.

let mut closure = capture_it::capture!([*a = 0], move || { a += 1; a });

assert_eq!(closure(), 1);
assert_eq!(closure(), 2);
assert_eq!(closure(), 3);

§capture struct fields

Under limited conditions, you can capture struct fields. The following expressions will capture each struct field as a copy and a reference, respectively.

struct Foo {
    copied: i32,
    borrowed: std::cell::Cell<i32>,
}

let mut foo = Foo { copied: 1, borrowed: 2.into() };
let closure = capture_it::capture!([foo.copied, &foo.borrowed], move || {
    copied + borrowed.get()
});

foo.copied = 9999;
foo.borrowed.set(3);
assert_eq!(closure(), 4);

§async blocks

All rules apply equally to the async move block.

let mut copied = 1;
let borrowed = std::cell::Cell::new(2);
let task = capture_it::capture!([copied, &borrowed], async move {
   copied + borrowed.get()
});

copied = 9999;
borrowed.set(3);

let val = futures::executor::block_on(task);
assert_eq!(val, 4);

§Shortcuts

There are various shortcuts for capturing variables for convenience.

  • Own: Call the ToOwned::to_owned method on the target variable.
  • Weak: Downgrades a std::rc::Rc or std::sync::Arc type to a Weak reference.
  • Some: Wrap cloned variable with Option. This is useful when you have to retrieve captured variable when it’s not a FnOnce closure.
  • All paths that do not fall under the above rules ($($p:ident)::*) are replaced with function calls.
  • You can simply write single method invocation on the captured variable, to capture the result of return value. capture!([foo.bar()], move || { ... }) -> in this case, let foo = foo.bar() will be captured into the closure.
#[cfg(not(feature = "no-std"))]
{
    use capture_it::capture;

    let hello = "hello, world!";
    let rc = std::rc::Rc::new(());
    let arc = std::sync::Arc::new(());
    let arc_2 = arc.clone();
    let hello_other = "hello, other!";

    let closure = capture!([Own(hello), Weak(rc), Weak(arc), arc_2, *hello_other.to_string()], move || {
        assert_eq!(hello, "hello, world!");
        assert!(rc.upgrade().is_none());
        assert!(arc.upgrade().is_some());
        assert_eq!(hello_other, "hello, other!");
    });

    drop((rc, arc));
    closure();
}