#closures #capture #macro

closure

A macro for capturing variables on a per variable basis

5 unstable releases

0.3.0 Mar 30, 2020
0.2.0 Aug 4, 2018
0.1.2 Aug 1, 2018
0.1.1 Aug 1, 2018
0.1.0 Aug 1, 2018

#760 in Rust patterns

Download history 1853/week @ 2024-06-12 1368/week @ 2024-06-19 1752/week @ 2024-06-26 1324/week @ 2024-07-03 1597/week @ 2024-07-10 1688/week @ 2024-07-17 2056/week @ 2024-07-24 1619/week @ 2024-07-31 1677/week @ 2024-08-07 1413/week @ 2024-08-14 964/week @ 2024-08-21 1477/week @ 2024-08-28 1164/week @ 2024-09-04 1205/week @ 2024-09-11 1007/week @ 2024-09-18 843/week @ 2024-09-25

4,443 downloads per month
Used in 21 crates (6 directly)

MIT license

16KB
184 lines

closure! - A macro for individually capturing variables

Latest version Documentation License

This crate provides a macro which lets you write closures that can capture individually either by moving, referencing, mutably referencing of cloning.

Usage

Start by adding an entry to your Cargo.toml:

[dependencies]
closure = "0.3.0"

Then you can write closures like so:

use closure::closure;

let string = "move".to_string();
let x = 10;
let mut y = 20;
let rc = Rc::new(5);

let closure = closure!(move string, ref x, ref mut y, clone rc, |arg: i32| {
    ...
});


lib.rs:

A macro for capturing variables on a per variable basis.

With this macro it is possible to specifically designate which variables will be captured by which method in a designated capture list. Variables can be either specified to be moved, referenced, mutably referenced or transformed using an arbitrary method identifier (e.g., clone). Any variables not specifically designated will be moved by default.

The syntax for each type of capture type is:

  • move var (moves var into the closure)
  • ref var (borrows var)
  • ref mut var (mutably borrows var)
  • $IDENT var (transforms var where $IDENT is any identifier for a method with a self receiver and no further arguments)

Move Binding

To capture a variable by moving it into the closure, use move or move mut to create a mutable binding:

let first = "first".to_string();
let second = "second".to_string();

let closure = closure!(move first, move mut second, || {
    // creates an immutable `first` and a mutable `second`
    // binding...
    # assert_eq!(first, "first");
    # second.clear();
    # assert_eq!(second, "");
});

Reference Binding

To capture a variable by borrowing it in the closure, use ref or ref mut for a mutable borrow, respectively.

let mut a = 1;
let b = 0;

let mut closure = closure!(ref mut a, ref b, || {
    *a = 0;
    assert_eq!(*a, *b);
});

Notice, that is also possible to capture named members of a struct, including in struct methods:

struct Foo {
    bar: i32,
}

impl Foo {
    fn print(&self) {
        // here a binding `let bar = &self.bar` will be
        // created for the closure
        closure!(ref self.bar, || println!("{}", bar))();
    }
}

This also applies to move captures, but the usual rules for destructuring apply.

$IDENT-transform Binding

Capturing a variable by an arbitrary identifier of a method with any self reciever (e.g., self, &self, &mut self, etc.) and no other arguments, creates a binding of the same name but the with the transformation method applied to the original variable. The most common use case for this type of capture is probably for calling clone() on a variable, but any method conforming to the aforementioned rules is also possible, such as to_string, to_owned, into_iter, etc.

let first = "first".to_string();
let second = "second".to_string();

let mut closure = closure!(clone first, clone mut second, || {
    // creates two bindings `first` and `second`,
    // the latter is mutable.
    println!("cloned: {}", first);
    second.clear();
    # assert_eq!(second, "");
});

closure();
println!("the original {} and {} were not moved", first, second);

Examples

Spawning a Thread

Instead of having to write:

use std::thread;
use std::sync::{Arc, Barrier, Mutex};

let mutex = Arc::new(Mutex::new(Vec::new()));
let barrier = Arc::new(Barrier::new(2));

let vector_clone = Arc::clone(&mutex);
let barrier_clone = Arc::clone(&barrier);

thread::spawn(move || {
    let mut vec = vector_clone.lock().unwrap();
    vec.push(2);
    vec.push(3);
    vec.push(4);

    barrier_clone.wait();
});

barrier.wait();
let mut vec = mutex.lock().unwrap();

vec.push(1);
assert_eq!(*vec, &[2, 3, 4, 1]);

Using closure! it becomes possible to avoid having to manually create bindings for each cloned Arc:

use std::thread;
use std::sync::{Arc, Barrier, Mutex};

use closure::closure;

let mutex = Arc::new(Mutex::new(Vec::new()));
let barrier = Arc::new(Barrier::new(2));

thread::spawn(closure!(clone mutex, clone barrier, || {
    let mut vec = mutex.lock().unwrap();
    vec.push(2);
    vec.push(3);
    vec.push(4);

    barrier.wait();
}));

barrier.wait();
let mut vec = mutex.lock().unwrap();

vec.push(1);
assert_eq!(*vec, &[2, 3, 4, 1]);

Moving cloned smart pointers into thread closures

From the documentation of Condvar:

use std::sync::{Arc, Mutex, Condvar};
use std::thread;

let pair = Arc::new((Mutex::new(false), Condvar::new()));
let pair2 = pair.clone();

// Inside of our lock, spawn a new thread, and then wait for it to start.
thread::spawn(move|| {
    let &(ref lock, ref cvar) = &*pair2;
    let mut started = lock.lock().unwrap();
    *started = true;
    // We notify the condvar that the value has changed.
    cvar.notify_one();
});

// Wait for the thread to start up.
let &(ref lock, ref cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
    started = cvar.wait(started).unwrap();
}

With closure!, the explicit declaration of pair2 can be avoided:

use std::sync::{Arc, Mutex, Condvar};
use std::thread;

use closure::closure;

let pair = Arc::new((Mutex::new(false), Condvar::new()));

// Inside of our lock, spawn a new thread, and then wait for it to start.
thread::spawn(closure!(clone pair, || {
    let &(ref lock, ref cvar) = &*pair;
    let mut started = lock.lock().unwrap();
    *started = true;
    // We notify the condvar that the value has changed.
    cvar.notify_one();
}));

// Wait for the thread to start up.
let &(ref lock, ref cvar) = &*pair;
let mut started = lock.lock().unwrap();
while !*started {
    started = cvar.wait(started).unwrap();
}

Mixing move and reference captures without having to specifically declare

the references which should not be moved

let move_string = "this string will be moved".to_string();
let mut ref_string = "this string will be borrowed".to_string();

let mut closure = closure!(move move_string, ref mut ref_string, || {
    ref_string.push_str(&move_string);
    //.. `move_string` is dropped at the end of the scope
});

Variable identifiers in the argument position (i.e., between the vertical lines) and return type specifications can also be used same as in regular closures.

Limitations

Any closure passed to the macro will implicitly become a move closure, so even variables that don't appear in the capture list but are used in the closure itself will also be moved into it.

No runtime deps