Rust book suggestion: add a section regarding Copy vs Move

IMO the following paragraph:
https://doc.rust-lang.org/book/ownership.html#copy-types
is not enough for the new learner to grasp this important concept.

I think a separate specific section should be added, including a list of Copy types, and explaining the internal details of what Rust does during a Copy or Move operation. Also some examples would be helpful.

Here are some links with useful information on this topic, which could be included in the section:

http://stackoverflow.com/questions/24253344/move-vs-copy-in-rust

2 Likes

To elaborate a little better on my suggstion above, IMO the section on copy vs move should be right after those explaining ownership, borrowing and mutability, because all these concepts are highly interrelated.

Here are a few examples that I think would help clarify the matter.

The first example is to explain why, for a copyable type such as [T; N], the following is allowed (notice the different mutability):

fn foo(mut x: [i32; 4]) {
    x = [1, 2, 3, 4];
    println!("x = {:?}", x);
}

fn main() {
    let a = [0; 4];
    foo(a);
    println!("a = {:?}", a);
}

and produces the output:

x = [1, 2, 3, 4]
a = [0, 0, 0, 0]

The second example is to explain that a non-copyable type such as Vec<i32> by default is instead moved (rather than copied) when passed to a function. Therefore the following is not allowed:

fn foo(mut x: Vec<i32>) {
    x = vec![1, 2, 3, 4];
    println!("x = {:?}", x);
}

fn main() {
    let a = vec![0; 4];
    foo(a);
    println!("a = {:?}", a);
}

To have the same behavior of the first example, the method .clone() should be explicitly called:

fn foo(mut x: Vec<i32>) {
    x = vec![1, 2, 3, 4];
    println!("x = {:?}", x);
}

fn main() {
    let a = vec![0; 4];
    foo(a.clone());
    println!("a = {:?}", a);
}

producing the output:

x = [1, 2, 3, 4]
a = [0, 0, 0, 0]

One option (but not a good choice in this case) to transfer the updated value from foo to main is to use an output parameter. This behaves similarly either if the type is copyable or non-copyable. Of course for this to work the variable a in main should be declared mutable as well, as in the following two examples:

fn foo(mut x: [i32; 4]) -> [i32; 4] {
    x = [1, 2, 3, 4];
    println!("x = {:?}", x);
    x
}

fn main() {
    let mut a = [0; 4];
    a = foo(a);
    println!("a = {:?}", a);
}
fn foo(mut x: Vec<i32>) -> Vec<i32> {
    x = vec![1, 2, 3, 4];
    println!("x = {:?}", x);
    x
}

fn main() {
    let mut a = vec![0; 4];
    a = foo(a);
    println!("a = {:?}", a);
}

which produce the same output:

x = [1, 2, 3, 4]
a = [1, 2, 3, 4]

It should be explained that although these two examples would seem to work differently, with the first one (copyable type) involving two copies and the second one (non-copyable type) involving two moves, they actually work quite similarly internally (a la memcpy).
Question: are there any performance differences in this case between the copy and the move operations as far as passing the arguments?

A better way to update the value of variable a in `main is to pass as reference. This works the same for copyable and non-copyable types, as in the following examples:

fn foo(x: &mut [i32; 4]) {
    *x = [1, 2, 3, 4];
    println!("x = {:?}", x);
}

fn main() {
    let mut a = [0; 4];
    foo(&mut a);
    println!("a = {:?}", a);
}
fn foo(x: &mut Vec<i32>) {
    *x = vec![1, 2, 3, 4];
    println!("x = {:?}", x);
}

fn main() {
    let mut a = vec![0; 4];
    foo(&mut a);
    println!("a = {:?}", a);
}

which produce the same output:

x = [1, 2, 3, 4]
a = [1, 2, 3, 4]

These last two examples allow also to talk about performance differences with respect to the previous two examples as far as passing arguments.

Finally it would be nice to explain why this works:

fn main() {
    let a = vec![0; 4];
    println!("a = {:?}", a);
    println!("a = {:?}", a);
}
3 Likes