Why this struct is not updated

struct Point {
    i: i32,
}

fn f1(update: impl FnOnce()) {
    update()
}

fn main() {
    let mut p = Point { i: 1 };
    f1(move || {
        p.i = 5;
    });
    dbg!(p.i);
}

The code will output 1.

I have two question:

  1. p is moved to the closure, why can I still print p from the outside.
  2. why does p not updated to 5.

Thanks,

This is because of the way closures are implemented in Rust.

You can think of a closure as a struct that gets automatically generated by the compiler and given some sort of call() method.

When you make a move closure, the compiler will find all the values that it refers to (i.e. p.i) and move them into some closure's internal state. In this case, you are only referring to p.i, which is an i32, so you can think of the compiler as generating something like this:

struct Closure {
  i: i32,
}

impl FnOnce<()> for Closure {
  type Output = ();
  fn call(mut self, args: ()) -> Self::Output {
    self.i = 5;
  }
}

When you look at it this way, it makes sense that doing p.i = 5 doesn't affect the original p object. The compiler just made a copy of the value of p.i and is updating that.

You can force the compiler to move the entire p value into the closure by doing something like this:

f1(move || {
    let mut p = p;
    p.i = 5;
});

However now you'll get a "use of moved value" error because the p value has been moved into the closure and can't be accessed by dbg!(p.i) any more.

error[E0382]: use of moved value: `p`
  --> src/main.rs:15:5
   |
10 |     let mut p = Point { i: 1 };
   |         ----- move occurs because `p` has type `Point`, which does not implement the `Copy` trait
11 |     f1(move || {
   |        ------- value moved into closure here
12 |         let mut p = p;
   |                     - variable moved due to use in closure
...
15 |     dbg!(p.i);
   |     ^^^^^^^^^ value used here after move
   |
   = note: this error originates in the macro `dbg` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0382`.

If you want the closure to be able to modify captured state, you'll probably want f1() to accept FnMut and not use move.

struct Point {
    i: i32,
}

fn f1(mut update: impl FnMut()) {
    update()
}

fn main() {
    let mut p = Point { i: 1 };
    f1(|| {
        p.i = 5;
    });
    dbg!(p.i);
}

(playground)

7 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.