Mastering Copy and Clone traits in Rust
Introduction
In Rust, the Copy and Clone traits are fundamental to managing value duplication. While they may seem similar, they represent two distinct concepts that are central to Rust's ownership model. #[derive(Copy, Clone)] is a common sight in Rust code, but understanding why both are needed and what they do is crucial for writing efficient and correct programs.
This document provides a comprehensive guide to Copy, Clone, and the #[derive] macro that enables them.
The Core Problem: Ownership and Duplication
By default, Rust uses move semantics. When you assign a value from one variable to another, ownership is transferred. The original variable becomes invalid and can no longer be used.
let s1 = String::from("hello");
let s2 = s1; // `s1` is moved to `s2`
// println!("{}", s1); // ❌ Compile Error: value borrowed here after move
This prevents issues like "double-free" errors, where two variables might try to deallocate the same memory. However, what if you actually want a duplicate of the value? This is where Clone and Copy come in.
Clone: Explicit Duplication
The Clone trait provides a universal mechanism for creating a duplicate of a value. It defines a single method, clone(), which returns a new instance of the type.
-
Explicit: You must call
.clone()to perform the duplication. - Potentially Expensive: The implementation can involve arbitrary code, such as allocating new memory on the heap and copying data.
Example: String
A String owns a heap-allocated buffer. Cloning it requires allocating a new buffer and copying the character data.
let s1 = String::from("hello");
let s2 = s1.clone(); // A new String is created on the heap.
println!("s1 = {}, s2 = {}", s1, s2); // Both s1 and s2 are valid.
Copy: Implicit, Trivial Duplication
The Copy trait is a special marker trait for types whose values can be safely and cheaply duplicated with a simple bit-for-bit copy.
-
Implicit: If a type is
Copy, assignments and function calls that would normally cause a move will instead perform a copy. The original value remains valid. -
Inexpensive: A
Copyoperation is just amemcpy. It does not execute any custom code.
Example: i32
Integers are simple bit patterns stored on the stack. They are Copy types.
let x = 42;
let y = x; // `x` is copied to `y`. No move occurs.
println!("x = {}, y = {}", x, y); // Both x and y are valid.
The Relationship: Why You Need #[derive(Copy, Clone)]
The Copy and Clone traits are intrinsically linked by a critical rule in Rust:
Every type that implements
Copymust also implementClone.
This is because Copy is a "supertrait" of Clone. The Copy trait itself has no methods; it's a marker that tells the compiler it's safe to perform implicit, bitwise copies. The Clone trait provides the explicit .clone() method.
For a Copy type, the implementation of clone() is trivial: it simply returns a copy of the value, effectively doing the same thing as the implicit copy.
You cannot implement Copy without Clone. Therefore, you almost always see them derived together: #[derive(Copy, Clone)].
#[derive(Copy, Clone)] // Correct: both are derived
struct Point {
x: f64,
y: f64,
}
// #[derive(Copy)] // ❌ Compile Error: `Copy` requires `Clone` to be implemented.
// struct AnotherPoint { ... }
Rules for Deriving Copy and Clone
The #[derive] macro can automatically generate the implementation for these traits, but only if certain rules are met.
#[derive(Clone)]
A type can derive Clone if all of its fields are also Clone. The derived implementation will simply call .clone() on each field to construct the new instance.
#[derive(Clone)]
struct MyData {
id: u32, // u32 is Clone
name: String, // String is Clone
coords: Vec<f64>, // Vec<f64> is Clone
}
If any field does not implement Clone, you cannot derive it and must implement it manually (or rethink your type's design).
#[derive(Copy)]
The rules for Copy are stricter. A type can derive Copy only if all of its fields are also Copy.
This is the key reason why types that own heap memory (like String or Vec) cannot be Copy. A bitwise copy would only duplicate the pointer/length/capacity struct on the stack, resulting in two variables pointing to the same heap allocation. This would violate Rust's ownership guarantees.
Comparison Table
| Type | Can be Clone? |
Can be Copy? |
Reason |
|---|---|---|---|
i32 |
Yes | Yes | Simple stack value. |
&'a str |
Yes | Yes | It's just a pointer and a length. |
(i32, bool) |
Yes | Yes | All its fields are Copy. |
String |
Yes | No | Owns heap memory. |
Vec<T> |
Yes (if T is Clone) |
No | Owns heap memory. |
struct Point {x:i32, y:i32} |
Yes | Yes | All its fields (i32) are Copy. |
struct Person {name: String} |
Yes | No | Contains a String, which is not Copy. |
Manual Implementation
When derive is not sufficient, you can implement the traits manually. This is common for Clone when you need custom logic, but rare for Copy, which is just an empty implementation.
Manual Clone
You might do this to implement a "shallow clone" or other custom duplication logic.
struct MyWrapper {
id: i32,
data: Vec<u8>,
}
// Custom Clone implementation
impl Clone for MyWrapper {
fn clone(&self) -> Self {
MyWrapper {
id: self.id, // i32 is Copy, so this is a copy
// For `data`, let's say we only want to clone the first half
data: self.data[0..self.data.len() / 2].to_vec(),
}
}
}
Manual Copy
Manually implementing Copy is just an empty block, as it's purely a marker trait. You still must implement Clone as well.
struct MyUnit;
impl Clone for MyUnit {
fn clone(&self) -> Self {
*self
}
}
impl Copy for MyUnit {} // Empty implementation
Using #[derive(Copy, Clone)] is equivalent to this manual implementation for types that qualify.
When to Use Copy vs. Clone
-
Use
Copyfor small, simple types that can be trivially duplicated. These are types that "feel like" values, such as coordinates, colors, or simple identifiers. ImplementingCopymakes them more ergonomic to use, as they can be passed around freely without worrying about ownership moves. -
Use
Clonefor types that own resources or require more complex duplication logic. This includes types that own heap memory (String,Vec,Box) or contain other non-Copydata. Calling.clone()signals that a potentially significant operation is happening. -
Prefer
&T(a borrow) overclone()when possible. If you only need to read the data, passing a reference is almost always more efficient than creating a full clone. Reserve cloning for when you truly need a new, owned instance of the data.
Top comments (0)