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 |
#822 in Rust patterns
5,369 downloads per month
Used in 22 crates
(7 directly)
16KB
184 lines
closure! - A macro for individually capturing variables
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
(movesvar
into the closure)ref var
(borrowsvar
)ref mut var
(mutably borrowsvar
)$IDENT var
(transformsvar
where $IDENT is any identifier for a method with aself
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.