I do not understand your question, but of course you are embarrassed. Therefore, I will turn to what seems to be the root of this confusion:
C ++ concepts about copying / moving I think I understood correctly, but it is “still memcpy anyway”, well, it wasn’t very intuitive when I read it
When we think about the semantics of moving Rust, ignore C ++ . The history of C ++ is much more complicated than Rust's, which is surprisingly simple. However, explaining Rust semantics in C ++ terms is a mess.
TL DR: Copies move. Movements are copies. The difference is only in the type controller. Therefore, when you want to “force movement” for type Copy , you ask for something that you already have.
So, we have three semantics:
let a = b where b not Copylet a = b where b Copylet a = b.clone() , where b is Clone
Note. There is no significant difference between assignment and initialization (for example, in C ++) - assigning only the first drop old value.
Note. Function call arguments work just like assignment. f(b) assigns b f .
Primarily.
a = b always executes memcpy .
This is true in all three cases .
- When you do
let a = b , b is memcpy 'd in a . - When you do
let a = b.clone() , the result of b.clone() is memcpy 'd in a .
Moves
Imagine b was Vec . A Vec as follows:
{ &mut data, length, capacity }
When you write let a = b , you end up with:
b = { &mut data, length, capacity } a = { &mut data, length, capacity }
This means that a and b both &mut data links, which means we have alias mutable data.
The type system is not like it, so it says that we cannot use b again. Any access to b will fail at compile time.
Note. a and b do not need to have a heap data alias to use as a bad idea. For example, both of them can be file descriptors - a copy will cause the file to be closed twice.
Note. Moves have extra semantics when destructors are involved, but the compiler will not let you write Copy for types with destructors.
Copy
Imagine b was Option<i32> . Option<i32> looks like this:
{ is_valid, data }
When you write let a = b , you end up with:
b = { is_valid, data } a = { is_valid, data }
They are both used simultaneously. To tell the type system that it is, mark Option<i32> as Copy .
Note. Marking something with a copy does not change what the code does. This allows more code. If you remove the Copy implementation, your code will either be a mistake or it will do the same. In the same vein, marking Copy as Copy will not change the compiled code.
Clones
Imagine you want to copy Vec . You implement Clone , which creates a new Vec and makes
let a = b.clone()
Two steps are performed. Let's start with:
b = { &mut data, length, capacity }
Running b.clone() gives us an extra temporary rvalue value
b = { &mut data, length, capacity } { &mut copy, length, capacity } // temporary
Running let a = b.clone() memcpy in a :
b = { &mut data, length, capacity } { &mut copy, length, capacity }
Further access to the temporary is thus prevented by the type system, since Vec not Copy .
But what about efficiency?
One thing that I skipped so far is that transfers and copies can be undone. Rust ensures that some trivial moves and copies are undone.
Since the compiler (after checking the life cycle) sees the same result in both cases, they are eliminated in exactly the same way.