How does move closure really work?

So I am learning about move closure today, and from the doc, move closure would get the value instead of the reference of a variable, which cannot be used outside later.

// This will not run
fn main() {
    let data = vec![1, 2, 3];
    let closure = move || println!("captured {:?} by value", data);

    println!("{:?}", &data);
}

However, the following code can compile fine. Why is that?

fn main() {
    let x = 10;
    
    let test = move | | {  println!("chec x value in closure: {}", x);};

    println!("check x {}", x);
    
    test()
}

When a type implements Copy, it gets copied instead of moved: The original location remains valid even after assigning the value to a new location. All of the primitive numeric types are Copy.

When a type does not implement Copy, the original location becomes uninitialized when you move the value to a new location. For example, a type like String will move instead of copy.

1 Like

In Rust, there's two kinds of types.

Types that implement the Copy trait, and types that don't.

When you pass a Copy type to a function, you can still use it.

let u32_is_Copy = 100u32;

fn function_that_takes_a_u32(u32_is_Copy);

// You can still use the value here!
println!("{}", u32_is_Copy);

Copy types are types that can be bit-wise copied when they need to be used somewhere in the program. This is the case for types for bool, u64, and all the common integer types.

But some types are not Copy. They cannot be copied trivally.

let Vec_is_not_Copy = vec![0, 1, 2];

fn function_that_takes_a_Vec(Vec_is_not_Copy);

// This doesn't work! The ownership of `Vec_is_not_Copy` has been given
// to the function. We are not allowed to use it anymore.
println!("{}", Vec_is_not_Copy);

Passing values to closures is the same exact thing. When you use a u32, its value is copied because u32: Copy. When you use a Vec<T>, it is moved, because Vec<T>: !Copy.

In other words, the Copy trait enables you to opt-out of Rust's ownership rules. Implementing Copy for a type is like saying to the compiler: "instances of this type do not carry any special meaning, they are just raw bytes".

The code you provided works fine without the move keyword because it's like you tried to call the functions by passing the values by reference.

fn function_that_takes_a_u32(&u32_is_Copy);
fn function_that_takes_a_Vec(&Vec_is_not_Copy);

In that case, both values can still be used. What have been copied to the function is a &u32 and &Vec respectively. Note that &T is Copy for any T.

// This works!
println!("{}", u32_is_Copy);
println!("{}", Vec_is_not_Copy);
4 Likes

Also note that the move and the copy are strictly identical operations at runtime. Both are bitwise copy(or shallow memcpy) which may or may not be elided during the optimization. The sole difference is that the compiler throws error if the moved out value is touched again, only if its type is not Copy.

2 Likes

Awesome, I never know using Copy can opt-out Rust ownership rules, and I have been confused for a long time. So I guess in general we should avoid Copy and stick with borrowing if possible, as copying should result in higher memory usage?

Borrowing will result in indirection (so possibly decreased performance) and, what's more important, in much more complex reasoning behind the code (it's very simple to get compiler confused and make it reject your code, if everything is a reference). Furthermore, types such as u8 will take less memory copied then borrowed.

1 Like

Thanks, so I guess I should copy when that type has a Copy trait. I don't quite understand your last sentence, if you borrow it then it will just pass the reference to that variable, and you don't need to create another block of memory in stack(using u8 as an example), then how come it will take less memory when copy?

Well, if type is not Copy, you won't be able to copy it anyway.

Reference is the "block of memory", too - at runtime it is represented as pointer, it's no magic.

A referece (i.e. &T or &mut T) are normal types. When you create a &T, Rust has to allocate space for it on the stack (often 4 or 8 bytes depending on your machine).

let a = 255u8;
let ref_to_a = &a;

println!("Size of 'a': {} byte" std::mem::size_of::<u8>());
println!("Size of `ref_to_a`: {} bytes", std::mem::size_of::<&u8>());

This will print the following output.

Size of 'a': 1 byte
Size of 'ref_to_a': 8 bytes // (or 4, depends on on your machine)

Moving a type (or copying it if it is Copy) will copy the bytes of the value somewhere else, usually on the stack. If you're type is larger than the size of a pointer (4 or 8 bytes), then it will be usually cheapter to directely copy the value itself. If it is larger, then maybe passing it by reference will be better.

Note that references result in indirections. Meaning that to access the referenced value, the program has to perform two read operations instead of one. The first read is used to load the reference (which is also an integer under the hood), and the second read actually loads the value you care about. Because if that, it will often be cheapter to pass u128 integers by value, even if it copies more data in the end.


For trivial types like bool, u16 or f32, it usually better to pass them by values (i.e. without borrowing them), For large types, like [u64; 128], you may want to create references instead.

For types that are not Copy, you won't have the choice because they follow Rust's ownership rules. Passing them by value will transphere ownership and you wont be able to access them anymore.

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.