`mut` binding required to mutate `move`-captured var in a closure

#1
fn main ()
{
    let x = 0;
    let mut captures_x = move || { x += 1; dbg!(x); };
    captures_x();
}

gives

error[E0594]: cannot assign to `x`, as it is not declared as mutable
 --> src/main.rs:4:36
  |
3 |     let x = 0;
  |         - help: consider changing this to be mutable: `mut x`
4 |     let mut captures_x = move || { x += 1; dbg!(x); };
  |                                    ^^^^^^ cannot assign

The mut-lint-ability of the initial binding should play no role in further mutability when the variable is moved elsewhere. For instance,

fn main ()
{
    let x = PrintOnDrop(0);
    {x}.0 += 1;
}

compiles and prints 1

So, in this case, captures_x is the binding that requires mut, and in theory, it could even not require it (it only requires so to be callable), not x (by the way, whether x is Copy or not plays no role here).

This looks like a bug to me, but before opening an issue I would like to have your thoughts on it.

1 Like

#2

Temporary values are always mutable. Same does not apply to assigned closures. It’s not worth trying to change how such items are specified. The argument would go onto dropping mut altogether.

Imaging two variables differing by mutability instead, you still have full specification of mutability. (Including to the closure variable.)

p.s. FnOnce don’t need the mut for the closure. (To keep with my original sentence; Guess you could say they are moved into a temporary, just like fn drop (&mut self) also needs that unspecified mutable.)

fn fnmut_fnonce(f:impl FnMut()) -> impl FnOnce() {f}
let captures_x = fnmut_fnonce(captures_x);
0 Likes

#3
struct CapturesX {
  x: i32,
}

impl CapturesX {
    fn call_mut (self: &'_ mut Self)
    {
        self.x += 1;
        dbg!(self.x);
    }
}

fn main ()
{
    let x = 0;
    let mut captures_x = CapturesX { x };
    captures_x.call_mut();
}

is a sketch of the unsugaring, which compiles since the mut (or lack thereof) of the initial x binding plays no role.

And for the FnOnce version:

impl CapturesX {
    fn call_once (mut self: Self)
    {
        self.x += 1;
        dbg!(self.x);
    }
}

fn main ()
{
    let x = 0;
    let captures_x = CapturesX { x };
    captures_x.call_once();
}

So I agree that the need to mut-ably bind the closure is not even required for a FnOnce call. But this further confirms that x does not need to be mut.

0 Likes

#4

Something could (and maybe should) be done in that direction: since &mut is a reference that is guaranteed to be unique, the actual purpose of the “mut-lint-ability” binding modifier is to make the binding non-unique aware. For basic examples this is fine, but with interior mutability or examples such as the one motivating this example, this design has its limits. I think that whilst this lint-guard can be useful, it should not hinder usability in more complex cases, with inconsistent patterns such as the aforementioned one. In other words: when in doubt, mut must not be required (like with temporaries being mut).

0 Likes