Copy vs Move
When you assign a value, pass it to a function, or return it, Rust must decide:
Do I copy the value, or do I move ownership?
Rust answers this at compile time, based on the type.
Copy Semantics
A type is Copy if:
- Its value can be duplicated bit-for-bit
- Copying is cheap and safe
- No heap memory needs to be managed
Common Copy Types
- Integers:
i32,u64 - Floating point:
f32,f64 bool,char- Tuples of
Copytypes - References (
&T,&mut Tare Copy, not the data!)
fn main() {
let x = 5;
let y = x;
println!("{}", x); // ✅ still valid
}
What happens in memory
xlives on the stackygets a full copy of the bits- Both values are independent
- No ownership transfer
Think: photocopying a number
Copy in Function Calls
fn takes_i32(n: i32) {
println!("{}", n);
}
let x = 10;
takes_i32(x);
println!("{}", x); // ✅ OK
Move Semantics
A move happens when:
- A value owns heap memory
- Copying would risk double free
- Ownership must be transferred
After a move:
- The original variable is invalid
- The new variable is the sole owner
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}", s1); // ❌ compile-time error
}
What happens in memory
Stack before move: Stack after move:
s1 ─┐ s2 ─┐
└─→ Heap "hello" └─→ Heap "hello"
- Only the pointer is moved
- Heap data is NOT copied
s1is invalidated
This prevents double free.
Move in Function Calls
fn takes_string(s: String) {
println!("{}", s);
}
let s = String::from("hello");
takes_string(s);
println!("{}", s); // ❌ error
Ownership moves into the function.
Why Rust Defaults to Move
Imagine Rust copied heap data automatically:
let s1 = String::from("hello");
let s2 = s1; // deep copy?
Problems:
- Expensive
- Unexpected performance cost
- Double-free risk if shallow copy
Rust chooses:
Move by default, copy only when explicitly safe
Clone vs Copy (Very Important)
Copy – implicit, cheap, automatic
let x = 5;
let y = x; // copy
Clone – explicit, potentially expensive
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}", s1); // ✅ OK
- Allocates new heap memory
- Copies the data
- You must ask for it
Rust makes you be explicit about cost.
Implementing Copy
A type can be Copy only if all its fields are Copy.
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
Now:
let p1 = Point { x: 1, y: 2 };
let p2 = p1;
println!("{}, {}", p1.x, p2.x); // ✅
But this is illegal:
struct Bad {
s: String, // ❌ not Copy
}
References: Copy but Not Ownership
let s = String::from("hello");
let r1 = &s;
let r2 = r1; // copy of reference
r1andr2both point tos- Ownership is unchanged
- Borrowing rules still apply
Partial Moves (Advanced but Useful)
struct User {
name: String,
age: u32,
}
let user = User {
name: String::from("Alice"),
age: 30,
};
let name = user.name; // move
println!("{}", user.age); // ✅ OK
namemovedagestill accessibleuseris partially invalid
Moves in Pattern Matching
let s = Some(String::from("hello"));
match s {
Some(value) => println!("{}", value), // move
None => {}
}
println!("{:?}", s); // ❌ error
Fix with borrowing:
match &s {
Some(value) => println!("{}", value),
None => {}
}
Copy vs Move Summary Table
| Aspect | Copy | Move |
|---|---|---|
| Implicit | ✅ | ✅ |
| Heap-safe | ❌ | ✅ |
| Performance cost | Tiny | Tiny |
| Heap allocation | ❌ | ❌ |
| Ownership transfer | ❌ | ✅ |
| Original usable | ✅ | ❌ |
Mental Model (This Sticks)
- Copy = duplicate bits
- Move = transfer responsibility
- Clone = deep copy (explicit)
- Ownership = who frees the heap
Rust doesn’t guess.
If it’s expensive or dangerous, you must say so.
Why This Matters for Memory Safety
Without move semantics:
- Double free bugs
- Use-after-free
- Hidden performance costs
Rust prevents all of these at compile time.