Move inside closure

fn main() {
    let x: i32;
    let printer = move |x:i32| { println!("x is: {}", x); };
    printer(2);
    x = 6; 
    println!("x is: {}",x);
}

(Playground)

Output:

x is: 2
x is: 6

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.92s
     Running `target/debug/playground`

Hello,

This is not an error.
If the move keyword is used, then all captures are by move or, for Copy types, by copy, regardless of whether a borrow would work.
All integral types, floating point types and boolean implement the Copy trait. So your x is copied, not referenced.

Even if you make a immutable reference you will still have the same result:

fn main() {
    let x: i32;
    let printer = move |x:&i32| { println!("x is: {}", x); }; // We do not modify x!!
    printer(&2); // Print 2
    x = 6; // Print 6
    println!("x is: {}",x);
}

printer(2) will print a temporary binded to the x of the closure, not the x of the main function.
As you can see here, we do not modify x of the main, because we are printing the reference to 2 that is binded to the x of the closure.

Take another example where the compiler tell you this is not the same variable:

fn main() {
    let x: i32;
    let printer = move |whatever: i32| { println!("whatever is: {}", whatever); };
    printer(2);
    println!("x is: {}",x); // ERROR: use of possibly-uninitialized `x`
}

The compiler is right, x is not modified by the closure.

2 Likes

If you give x to a closure, then the ownership of X has not changed, right, because it is a scalar type. If it is a string or array type, then the ownership of X will be given to the variables in the closure, and it can no longer be used later. Is that right?

You mean something like this : passing array to closure

fn main() {
    let x:i32 = 5;
    let printer = move |whatever: i32| { println!("whatever is: {}", whatever); };
    printer(x);
    println!("x is: {:?}",x);
}

fn main2() {
    let x:String = String::from("rust");
    let printer = move |whatever: i32| { println!("whatever is: {}", whatever); };
    printer(x);
    println!("x is: {:?}",x);
}

main2() cannot be compiled

fn main() {
    let x: [i32;1];
    let printer = move |whatever: i32| { println!("whatever is: {}", whatever); };
    printer(2);
    x = [4];
    println!("x is: {:?}",x);
}
Compiling playground v0.0.1 (/playground)
error[E0382]: borrow of moved value: `x`
 --> src/main.rs:5:27
  |
2 |     let x:String = String::from("rust");
  |         - move occurs because `x` has type `String`, which does not implement the `Copy` trait
3 |     let printer = move |whatever: String| { println!("whatever is: {}", whatever); };
4 |     printer(x);
  |             - value moved here
5 |     println!("x is: {:?}",x);
  |                           ^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0382`.
error: could not compile `playground` due to previous error

fix:

let printer = move |whatever: String| { println!("whatever is: {}", whatever); };

You can read more about ownership: https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html

1 Like

I will quote the reference book here:

The compiler prefers to capture a closed-over variable by immutable borrow, followed by unique immutable borrow (see below), by mutable borrow, and finally by move
....
If the move keyword is used, then all captures are by move or, for Copy types, by copy, regardless of whether a borrow would work. The move keyword is usually used to allow the closure to outlive the captured values, such as if the closure is being returned or used to spawn a new thread.

Now you have to know what you are moving. Is it a reference to a value? or the value itself.
You can get a reference to the value without move (Because reference is like C pointer with extra and is copied)

Here I don't use move and it compiled because I capture a reference to the Vec, not the Vec. Try with the move, it make no difference because the reference is copied, not the Vec.
Here I do use the move keyword and it does not compiled. Even with the move keyword, because Vec is not copied, it's moved even without move because it doesn't implement the Copy trait

2 Likes

Yes, you are right. Of course, i can also use the smart pointer Box:: new() It bypasses the check

1 Like

For the record, in case that isn't clear: in the original code example in this thread, the closure captures no variable at all because the closure argument name shadows the local variable x that exists outside of the closure. The x in println call in the closure body refers to the closure argument, and no variable is captured at all. The move has no effect, and for that example alone, the information how move interacts with Copy types is irrelevant.

4 Likes

I always forgot about shadowing but the "problem/output" still the same if the closure variable is named w. I think the incomprehension here is about the lack of understanding of the move keyword than variable shadowing.

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.