Search code examples
rustclosuresimmutability

Why can't I mutably move-capture an immutable variable?


I have a function creating a FnMut closure, move-capturing one of it's arguments. I get a compiler error because the argument isn't mut. I don't get what the problem is, as I move it and the closure isn't really using the argument itself.

Code:

pub struct Position {
    pub x: usize,
    pub y: usize,
}

fn map_it(p: Position) -> impl FnMut(usize) -> usize {
    move |param| {
        p.x += 1;
        p.x + p.y + param
    }
}

Error:

error[E0594]: cannot assign to `p.x`, as `p` is not declared as mutable
 --> src/lib.rs:8:9
  |
6 | fn map_it(p: Position) -> impl FnMut(usize) -> usize {
  |           - help: consider changing this to be mutable: `mut p`
7 |     move |param| {
8 |         p.x += 1;
  |         ^^^^^^^^ cannot assign

Solution

  • Even though the variable p has been moved, you didn't introduce a new variable binding. The binding p is not mutable, so you cannot mutate the value through p.

    The closure is really a struct that looks (a bit) like this:

    struct MyClosure {
        p: Position,
    }
    
    impl FnMut<usize, usize> for MyClosure {
        fn call_mut(&mut self, param: usize) -> usize {
            self.p.x += 1;
            self.p.x + p.y + param
        }
    }
    

    From this, it's definitely compelling that p should be mutable from inside the closure.

    However, Rust avoids directly exposing this implementation detail. The mutability semantics of the variable p are lexical, exactly as if the body of the closure was part of the outer function.

    There is nothing you can do to make this variable mutable from within the closure. You instead need to make the original p mutable or introduce a new mutable binding outside the closure.