Search code examples
rustclosures

Is x in the code copied or taken as a mutable ref in the closure?


I am basically trying to understand why add_to_x has a trait of FnOnce.

let mut x = 5;
let mut add_to_x = |n: i32| {
    x += n;
    x
};
let m = add_to_x(5);
println!("x: {}", x);
println!("m: {}", m);

Also, if I assign add_to_x to another variable, would the closure be copied or moved?


Solution

  • By default, in closures, Rust passes values by reference. x in the closure behaves as a &mut x, except its type is still i32 (it is dereferenced automatically by the compiler whenever you use it).

    In particular, since &mut T: !Clone for any T, add_to_x is not clonable (in particular, it cannot be copied). It's easy to show this in an example:

    fn main() {
        let mut x = 5;
        let mut add_to_x = |n: i32| {
            x += n;
            x
        };
        let mut add_to_x_bis = add_to_x;
        let m = add_to_x(5);
        let _ = add_to_x_bis(5);
        println!("x: {}", x);
        println!("m: {}", m);
    }
    

    (see the playground).

    This will fail with the following error message, which is pretty clear

    error[E0382]: borrow of moved value: `add_to_x`
     --> src/main.rs:8:13
      |
    7 |     let mut add_to_x_bis = add_to_x;
      |                            -------- value moved here
    8 |     let m = add_to_x(5);
      |             ^^^^^^^^ value borrowed here after move
      |
    note: closure cannot be moved more than once as it is not `Copy` due to moving the variable `x` out of its environment
    

    Think about what would happen if it was possible to have two instances of add_to_x valid at the same time. The two of them would be holding an exclusive reference to x, which is forbidden.

    Hence, to answer your question, the closure is moved.

    Besides, add_to_x is an FnMut, which is stronger than FnOnce. FnOnce means that the closure can be executed once. It's the only guarantee that this trait provides. FnMut means that the closure can be executed any number of times, but the call method takes a mutable reference to self, that is to say, you need to be able to produce a mutable reference to the closure to call it (hence the requirement of add_to_x to be a mutable variable).

    In this case, add_to_x mutates something that is behind an exclusive borrow, but it can be called several times, so it's an FnMut closure.