sergmister
sergmister

Reputation: 325

Closure is `FnOnce` because it moves the variable `________` out of its environment

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

Answers (2)

user4815162342
user4815162342

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:

  1. move ensures that the data value is moved into the closure, and does not outlive the closure.

  2. 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

Kevin Reid
Kevin Reid

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:

  • If the value implements the 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.)
  • If you call a method such as .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

Related Questions