Move and Copy are two behaviors when transferring values via an assignment or a function call.
In the previous article, we dropped a bombshell: “If you assign a String to another variable, the original stops working”.
This directly clashes with our intuition from other languages.
- If I do
a = b, I expectaandbto hold the same value and both be usable. - In Rust, depending on the data type, this can be a Copy or a Move.
Understanding this distinction is crucial to stop fighting the compiler. Today, we’ll see what happens under the hood 👇.
The Default Behavior: Move
Let’s analyze the case of complex types that use the Heap, like String.
let s1 = String::from("hello");
let s2 = s1; // <-- The Move happens hereConceptually, the representation of a String in Rust contains three parts:
- A pointer to the memory on the Heap (where the letters ‘h’, ‘e’, ‘l’, ‘l’, ‘o’ are stored).
- The length (how much memory we are currently using).
- The capacity (how much memory we have allocated).
When we do let s2 = s1, Rust transfers this representation to s2. It does NOT copy the data on the Heap.
If Rust stopped there, we would have what other languages call a Shallow Copy. We would have two variables (s1 and s2) pointing to the same place on the Heap.
Let’s remember the core idea of Ownership: only one owner at a time. If Rust allowed s1 and s2 to exist simultaneously, both would try to free the same memory when going out of scope (Double Free error).
To prevent this, Rust does something drastic: it marks s1 as invalid.
This is a Move. The data hasn’t been physically moved in RAM (it’s still at the same Heap address), but ownership has been transferred.
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1); // ❌ Error: value borrowed here after moveImagine you own a house and have the deed. If you give the deed to your brother, the house hasn’t moved, but you are no longer the owner. You can’t enter. He is the owner now.
When We Need Duplicates: Clone
What if we really want two independent copies of the data? That is, we want to copy the Stack structure AND ALSO the data on the Heap.
For String, this involves a deep copy and doesn’t happen automatically because it requires memory allocation. If you want to do it, you must ask for it explicitly using the .clone() method.
let s1 = String::from("hello");
let s2 = s1.clone(); // <-- Explicit deep copy
println!("s1 = {}, s2 = {}", s1, s2); // ✅ WorksWhen we see .clone(), we know the type has defined an explicit duplication. For String, it copies the content, though not all clone operations are equally expensive (cloning an Rc<T>, for example, just increments a counter).
The Exception to the Rule: Copy Types
Now we come back to the case that confused us earlier. Why does this work?
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y); // ✅ Works perfectlyWhy hasn’t x been invalidated? Don’t integers have owners?
Copying an integer simply requires duplicating a few bits. Also, it doesn’t have any special destruction logic that could run twice.
Rust has a special trait called Copy.
- If a type implements
Copy, when assigned it duplicates instead of moving. - The original variable remains valid.
How to Know What Happens
When you see an assignment let a = b; or you pass a variable to a function funcion(b);, ask yourself:
- Does the type implement
Copy? The assignment duplicates the value, and both names remain valid. - Does it not implement
Copy? The assignment moves the value, and the previous name becomes unusable. - Do you need two independent values? Check if the type implements
Cloneand evaluate the cost of calling.clone().