A coworker once asked me: “If Rust moves everything, how do I ever use a value twice?” Fair question. The answer is two traits that look similar but behave very differently — Copy and Clone.
Getting these confused will either tank your performance or confuse the hell out of you. Probably both.
Copy: Implicit, Cheap, and Bitwise
Copy is a marker trait. It tells the compiler: “this type is so cheap to duplicate that you should do it automatically on assignment instead of moving.”
fn main() {
let x: i32 = 42;
let y = x; // x is copied, not moved
println!("{} {}", x, y); // both valid
let a = 3.14_f64;
let b = a; // copied
println!("{} {}", a, b); // both valid
let flag = true;
let other = flag; // copied
println!("{} {}", flag, other); // both valid
}
No method call. No explicit syntax. It just happens. That’s the whole point of Copy — it’s invisible.
What types are Copy?
All the primitive, stack-only types:
- Integers:
i8,i16,i32,i64,i128,isize - Unsigned:
u8,u16,u32,u64,u128,usize - Floats:
f32,f64 bool,char- Tuples of
Copytypes:(i32, f64, bool) - Fixed-size arrays of
Copytypes:[i32; 5] - References:
&T(but not&mut T) - Function pointers
What types are NOT Copy?
Anything that owns heap memory or has a custom Drop implementation:
String(owns heap-allocated bytes)Vec<T>(owns heap-allocated buffer)Box<T>(owns heap-allocated value)HashMap<K, V>- Any type that implements
Drop
The rule is logical: if copying the bits would result in two owners of the same heap allocation, it can’t be Copy. You’d get a double free.
Clone: Explicit and Potentially Expensive
Clone is for when you genuinely want a deep copy and you’re willing to pay for it.
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // explicit deep copy
println!("{}", s1); // still valid
println!("{}", s2); // independent copy
}
.clone() allocates new heap memory, copies the bytes over, and gives you a completely independent value. For a String with a megabyte of text, that’s a megabyte of allocation and copying. Not free.
The explicit .clone() call is a feature, not a limitation. It’s a neon sign in your code that says “ALLOCATION HAPPENING HERE.” In C++, this might happen silently in a copy constructor you forgot about. In Rust, you asked for it.
The Problem: Hidden Performance Traps
Here’s the naive code that’ll kill your performance:
fn process_name(name: String) -> String {
format!("Hello, {}!", name)
}
fn main() {
let names = vec![
String::from("Alice"),
String::from("Bob"),
String::from("Charlie"),
];
// BAD: cloning every string just to print it
for name in &names {
let greeting = process_name(name.clone());
println!("{}", greeting);
}
}
Every .clone() is a heap allocation. Three names, three unnecessary allocations. The fix? Take a reference instead:
fn process_name(name: &str) -> String {
format!("Hello, {}!", name)
}
fn main() {
let names = vec![
String::from("Alice"),
String::from("Bob"),
String::from("Charlie"),
];
// GOOD: borrowing — zero allocations for the names
for name in &names {
let greeting = process_name(name);
println!("{}", greeting);
}
}
If you’re scattering .clone() calls everywhere to make the borrow checker happy, that’s a design smell. You’re paying runtime cost to avoid compile-time thinking.
Implementing Copy and Clone for Your Types
You can derive both:
#[derive(Debug, Copy, Clone)]
struct Point {
x: f64,
y: f64,
}
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1; // copied implicitly (Copy)
let p3 = p1.clone(); // also works (Clone)
println!("{:?} {:?} {:?}", p1, p2, p3);
}
But you can only derive Copy if all fields are Copy:
// This WON'T compile — String is not Copy
// #[derive(Copy, Clone)]
// struct User {
// name: String, // not Copy!
// age: u32,
// }
// This works — only Clone, not Copy
#[derive(Debug, Clone)]
struct User {
name: String,
age: u32,
}
fn main() {
let u1 = User {
name: String::from("Atharva"),
age: 28,
};
let u2 = u1.clone(); // must be explicit
println!("{:?}", u1);
println!("{:?}", u2);
}
Custom Clone Implementations
Sometimes the derived Clone isn’t what you want. Maybe you need to reset some fields, or do a partial clone:
#[derive(Debug)]
struct CacheEntry {
key: String,
value: String,
access_count: u64,
}
impl Clone for CacheEntry {
fn clone(&self) -> Self {
CacheEntry {
key: self.key.clone(),
value: self.value.clone(),
access_count: 0, // reset on clone — fresh copy shouldn't inherit count
}
}
}
fn main() {
let entry = CacheEntry {
key: String::from("user:123"),
value: String::from("cached data"),
access_count: 42,
};
let fresh = entry.clone();
println!("Original count: {}", entry.access_count); // 42
println!("Clone count: {}", fresh.access_count); // 0
}
The Copy/Clone Relationship
Here’s a rule that trips people up: every Copy type must also implement Clone, but not every Clone type is Copy.
Copy is a subtrait of Clone:
pub trait Copy: Clone { }
That means if you implement Copy, you must also implement Clone. But the reverse isn’t true — you can be Clone without being Copy.
Think of it as: Copy is the “automatic, cheap” version. Clone is the “manual, maybe expensive” version. If something can be auto-copied, it can certainly be manually cloned too.
When to Derive Copy
Derive Copy when:
- Your type is small and stack-only (like
Point { x: f64, y: f64 }) - All fields are
Copy - Implicit duplication is the expected behavior
- You have no custom
Dropimplementation
Don’t derive Copy when:
- Your type manages a resource (file handle, connection, lock)
- Implicit copying would be surprising or expensive
- You want to enforce single ownership semantics
// Good candidate for Copy — small, value-like
#[derive(Copy, Clone, Debug)]
struct Color {
r: u8,
g: u8,
b: u8,
a: u8,
}
// Bad candidate for Copy — even if it were technically possible
// A mutex guard should NOT be implicitly copied
// (And it can't be anyway, since it has Drop)
Clone Is Not Free — Measure It
I benchmarked this in a real project once. We had a function that was cloning a Vec<String> with 10,000 elements on every call. Switching to a reference cut that function’s runtime by 80%. Eighty percent. All from removing one .clone().
// BEFORE: cloning a large vec every call
fn analyze(data: Vec<String>) -> usize {
data.iter().filter(|s| s.len() > 10).count()
}
// AFTER: borrowing instead
fn analyze(data: &[String]) -> usize {
data.iter().filter(|s| s.len() > 10).count()
}
Same result. Radically different performance. The compiler couldn’t optimize away that clone because it can’t prove the original won’t be modified.
The Decision Tree
When you need to use a value more than once:
- Can you borrow it? Use
&T. Zero cost. This should be your default. - Do you actually need a separate owned copy? Use
.clone(). - Is the type small and stack-only? Consider implementing
Copyso it happens automatically. - Are you cloning to satisfy the borrow checker? Rethink your data structures. You might be fighting the wrong battle.
.clone() is a tool, not a crutch. Use it when you genuinely need independent copies. Don’t use it to paper over ownership problems — fix those problems at the design level instead.
Next up: the borrow checker itself — what it’s actually doing and why it rejects your code.