Reputation: 325
I've been having some issues with moving reference counted variables into closures which need to implement FnMut
.
The following code works fine:
use std::rc::Rc;
fn main() {
let callback;
let data: Rc<Vec<f32>> = Rc::new(Vec::new());
{
let data = data.clone();
callback = move || {
data.iter().for_each(|v| println!("{}", v));
};
consumer(callback);
}
}
fn consumer<D>(callback: D)
where
D: FnMut(),
{
}
If I change the closure to:
callback = move || {
data;
};
The compiler produces the error:
--> src/test1.rs:11:20
|
11 | callback = move || {
| ^^^^^^^ this closure implements `FnOnce`, not `FnMut`
12 | data;
| ---- closure is `FnOnce` because it moves the variable `data` out of its environment
...
15 | consumer(callback);
| -------- the requirement to implement `FnMut` derives from here
This example works:
callback = move || {
let x = data[0];
println!("{}", x);
};
This example does not:
callback = move || {
let x = data;
println!("{}", x[0]);
};
With let data: Arc<Mutex<Vec<f32>>> = Arc::new(Mutex::new(Vec::new()));
, this works:
callback = move || {
let x = data.lock().unwrap();
};
This does not:
callback = move || {
let x = data;
};
What is the reasoning behind this?
Upvotes: 12
Views: 6022
Reputation: 154886
To cover the same topic from a slightly different angle than the accepted answer - there are two concepts that are related but distinct:
move
ensures that the data
value is moved into the closure, and does not outlive the closure.
the above is independent of whether merely calling the closure needs to consume data
.
In case of move || data.lock().unwrap()
, your closure's call implementation takes &self
, and can be called multiple times, destroying data
only when the closure is dropped.
In case of move || data
, the closure's call implementation takes self
and immediately consumes data (and the rest of the closure), which is why it can be called only once, and will drop data
by the time it returns.
The case in #2, the body of the closure consuming the data (move || data
or just || data
) implies that the closure takes ownership of the captured value. The reverse doesn't apply: the mere fact that you used move
to force the closure to take over the ownership of the value doesn't automatically mean that the call will consume the value. But if you wish, you can always force that by doing something like drop(data)
in the closure body, which will make your closure FnOnce
.
Upvotes: 3
Reputation: 43753
The following code works fine:
let data: Rc<Vec<f32>> = Rc::new(Vec::new()); { let data = data.clone(); callback = move || { data.iter().for_each(|v| println!("{}", v)); };
First, when the closure is constructed, data
is moved into it, because the closure is declared as move ||
. That's why you need to clone beforehand if you want to use data
again outside the closure
Then later, when the closure is called, iter()
is a method that takes &self
, so data
is not moved out by the method call; it is borrowed, and the closure still owns it. Thus, the closure can be called as many times as you want — it automatically implements Fn
and not just FnOnce
.
callback = move || { data; };
callback = move || { let x = data; println!("{}", x[0]); };
Here, when the closure is called, data
is moved (used by value, not by reference). In the first case, it is immediately dropped; in the second it is moved into the local variable x
.
If a value is moved out of a closure, then the closure only implements FnOnce
, and can only be called once, because if the closure were called a second time, it wouldn't have that value to use any more.
In general, any plain mention of a variable name is a move. Everything else is a specific exception:
Copy
trait then it can be a copy instead of a move, which means the original stays valid. (Does not apply here, since Rc
does not implement Copy
.).iter()
or .lock()
, the method can take &self
or &mut self
; Rust automatically takes a reference as if you had written (&data).iter()
instead.println!
is a macro which implicitly takes references to its arguments, for convenience.If you write &data;
or let x = &data;
instead, then you will have created a reference, which does not move the original value.
Upvotes: 9