Copy Types - The Exception to Moves
Learn what Copy types are, why they exist as an exception to Rust's move semantics, which standard types implement Copy, and how to explicitly copy data with the Clone trait.
When Rust assignment moves ownership for heap‑allocated data like String, why does something as simple as let y = x; with an integer still work? The language has an intentional exception for types that are trivial to duplicate — types that live entirely on the stack and carry no resource‑management responsibility. These types implement the Copy trait, and their behavior is the subject of this page.
What Copy Types Are
A Copy type is any type that the compiler is allowed to duplicate by a simple bit‑for‑bit copy instead of performing a move. When you write let y = x; and x is of a Copy type, Rust copies the bits of x into y on the stack and leaves x fully usable. No ownership transfer happens, no value gets invalidated, and both variables hold independent copies of the same value.
The defining rule: every type that implements the Copy trait is exempt from move semantics in assignment and pass‑by‑value contexts.
fn main() {
let a: i32 = 7;
let b = a; // `a` is Copy, so it is bitwise copied
println!("a = {}, b = {}", a, b); // both `a` and `b` are valid
}
Without the Copy trait, the same assignment would move ownership, making a unusable afterward. That would be unreasonable for numeric types — and it would violate the principle that ownership only matters for values that own resources.
Check your understanding:
If you run the snippet above and see both values printed, the type you’re using is Copy. That’s the quickest way to verify whether a type participates in move semantics or not.
Why Copy Types Exist
Imagine every integer had to be moved: you’d have to write let y = x.clone() for every let y = x. Readability and ergonomics would suffer, and the safety guarantee Rust offers would become a burden for the most common data in a program.
The Rust designers recognized that a u32 stored on the stack is just 4 bytes. Duplicating it costs a trivial memcpy — no system resources, no heap, no custom destructor. There is no “double‑free” risk because there is nothing to free. By marking types like these as Copy, Rust automatically applies a cheap, predictable duplication and lets the programmer ignore ownership for those values.
This exception also aligns with how developers already think about simple data. The mental model is: numbers, booleans, and characters are values — they behave like sticky notes you can photocopy; strings, vectors, and boxes are resources — they behave like unique library books you hand over.
How Copy Semantics Work Mechanically
From the compiler’s perspective, every assignment or function call that passes a value by‑value first checks whether the type implements Copy. If it does, the compiler generates a bitwise copy. If it doesn’t, the compiler moves the stack data (the “handle”) and invalidates the source binding.
Consider the two cases side by side:
// Type that is NOT Copy (String)
let s1 = String::from("hello");
let s2 = s1; // move: s1 is invalidated
// println!("{}", s1); // COMPILE ERROR
// Type that IS Copy (i32)
let n1 = 42;
let n2 = n1; // copy: both bindings stay valid
println!("{} {}", n1, n2);
For String, the stack holds a pointer, a length, and a capacity. The move copies those three fields to s2, then invalidates s1 — only one owner ever points to the heap buffer. For i32, there is no heap; the stack contains the integer directly, so copying the bits creates two independent, equal values. No invalidation needed.
This design means the compiler can decide purely from the type whether a copy is safe. There is no runtime overhead for deciding; it’s all static dispatch.
Types That Implement Copy
The standard library implements Copy for types that are completely stack‑allocated and do not manage resources that need cleanup. Here are the most common categories:
| Category | Examples | Copy? |
|---|---|---|
| Integer types | u8, i32, usize, isize | Yes |
| Floating‑point types | f32, f64 | Yes |
| Boolean | bool | Yes |
| Character | char | Yes |
| Immutable references | &T | Yes |
| Tuples of all‑Copy elements | (i32, bool), (char, f64) | Yes |
| Arrays of Copy element | [u8; 5], [bool; 10] | Yes |
| Strings, vectors, boxes | String, Vec<T>, Box<T> | No |
| Mutable references | &mut T | No |
&mut T is not Copy:
Immutable references (&T) are Copy because they only read — multiple &Ts can exist at the same time. A mutable reference (&mut T) is exclusive, so copying it would violate the single‑writer rule. Assigning let r2 = r1; moves a mutable reference and invalidates r1.
Custom structs and enums can also be Copy if and only if all their fields are Copy and they do not implement the Drop trait. The easiest way to opt in is with a derive macro:
#[derive(Copy, Clone, Debug)]
struct Point {
x: f64,
y: f64,
}
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1; // `Point` is Copy, so `p1` stays usable
println!("{:?}", p1);
println!("{:?}", p2);
}
All fields (f64) are Copy, there is no Drop, and Clone is required as well because Copy is a subtrait of Clone. If the struct contained a String, the derive would fail with a message like the trait Copy may not be implemented for this type.
The Clone Trait: Explicit Copying
For non‑Copy types, duplication is still possible — but it must be explicitly requested through the Clone trait. While Copy silently duplicates bits, Clone is a general‑purpose mechanism that may allocate new memory, perform deep copies, or run custom logic.
let original = String::from("Rust");
let duplicate = original.clone(); // explicit deep copy
println!("original: {original}");
println!("duplicate: {duplicate}");
The call to .clone() allocates a new heap buffer, copies the string bytes into it, and returns a fresh String that independently owns the new memory. Both original and duplicate are valid.
Clone and Copy relate but are not interchangeable:
CopyrequiresClone(everyCopytype must implementClone).Clonedoes not implyCopy.StringisClonebut notCopy.- You can implement
Clonefor any type that knows how to duplicate itself.Copyis restricted to types where a bitwise copy is correct.
Drop and Copy cannot coexist:
If a type has custom cleanup logic in a Drop implementation, it cannot be Copy. The reasoning: if a value is bitwise copied and both copies go out of scope, Rust would call drop on both — causing a double‑free or resource mismanagement. The compiler rejects any type that implements both Drop and Copy.
This restriction is one of the most effective guards in the language. It prevents you from accidentally marking a resource‑owning type as Copy.
Deriving Clone Without Copy
When a struct contains a non‑Copy field like String, you can still derive Clone to get an explicit deep‑copy method, but you cannot derive Copy:
#[derive(Clone, Debug)]
struct Label {
name: String,
id: u32,
}
fn main() {
let l1 = Label {
name: String::from("anchor"),
id: 10,
};
let l2 = l1.clone(); // l1 is still usable, but only because of Clone
println!("{:?} / {:?}", l1, l2);
}
Common Mistakes with Copy and Move
Assuming a type is Copy based on its size.
String is three words on the stack but is not Copy because it manages heap memory. The trait is about resource ownership, not size.
Forgetting that &mut T is not Copy.
let r1 = &mut x; let r2 = r1; moves the mutable reference. r1 becomes invalid, and you cannot use it again. This prevents you from accidentally creating two mutable borrows.
Implementing Copy for a type with a manual Drop.
The compiler will stop you with a clear error: the trait Copy may not be implemented for this type. This is not a bug — it’s a safety guarantee.
Expecting Clone to be automatic.
Even if you think a type could be trivially copied, Rust will never insert a .clone() call for you. The visible .clone() is a deliberate signal that a potentially expensive operation is happening.
Summary
Copy types are the language’s acknowledgment that not all data needs the full ownership machinery. They let simple stack‑only values be duplicated implicitly, keeping numeric and trivial code concise and predictable, while types that own resources continue to uphold Rust’s memory safety through move semantics.
The distinction between Copy and Clone is crucial: Copy is a marker that says “a bitwise copy is always correct,” while Clone is a method that performs any necessary deep‑copy work on demand. Together they give you fine‑grained control over duplication: automatic where safe, explicit where meaningful.