Copy Trait

How Rust's Copy trait enables automatic bitwise duplication of values, the rules for implementing it, and its impact on ownership and assignment.

Copy Trait

Copy is a marker trait that changes how Rust handles variable assignment for a type. When a type implements Copy, assigning it to another variable, passing it to a function, or returning it from a function does not move the original value. Instead, the value is duplicated bit‑for‑bit, and the original remains usable.

If it compiles, it works:

Once you derive Copy (and the compiler accepts it), every field in your type is Copy and no custom destructor exists. You can safely pass values around without worrying about moves or explicit cloning.

Why Copy Exists: Escape from Move‑Only Assignments

Rust’s default ownership model is move semantics. When you write let y = x;, ownership transfers to y, and x is no longer valid. This prevents double‑frees for heap‑allocated data but becomes tedious for simple stack‑only values. A plain integer shouldn’t force you to call .clone() or carefully track ownership after every assignment.

Copy solves that friction. It tells the compiler: “This type is so trivial to duplicate that you should just copy the bytes automatically, and let both variables live independently.”

let a: i32 = 10;
let b = a;           // a is *copied*, not moved
println!("{a}");     // perfectly fine — a is still valid

Without Copy, even i32 would behave like String — one owner, one valid variable after assignment. The standard library implements Copy for i32 so developers never have to think about moves for plain numbers.

How Copy Transforms Assignment

Under the hood, a copy is a memcpy of the value’s bytes from one stack location to another. The critical difference from a move is not the memory operation itself (moves often copy bytes as well) but the compiler’s permission to keep using the source variable.

When a type is Copy, the compiler treats an assignment like let y = x; as an instruction to create an independent duplicate. After the copy, x and y are two distinct values; modifying one never affects the other.

Copy vs. Move, Side by Side

// i32 is Copy → implicit duplication
let n1 = 256;
let n2 = n1;          // n1 is copied
println!("{n1}");     // works
// String is not Copy → move semantics
let s1 = String::from("hello");
let s2 = s1;          // s1 is moved; s1 is now invalid
// println!("{s1}");  // compile error: value borrowed after move

A beginner mental model: think of Copy types like photocopying a document — you get an identical sheet, and the original remains on your desk. Non‑Copy types are like handing over the only physical original; once you give it away, you no longer have it.

Copy doesn't mean 'free duplication for everything':

The automatic duplication only applies to the value itself. A Vec<i32> is still a heap‑allocated structure and is not Copy, even though its elements (i32) are. Moving a Vec transfers the whole vector; the individual i32s inside don't get a chance to copy themselves independently.

The Relationship with Clone

Copy has one hard requirement: any type that implements Copy must also implement Clone. Clone is a supertrait of Copy. The reasoning is straightforward: Copy provides implicit duplication, but a developer may occasionally want to be explicit by calling .clone(). Implementing Clone ensures that call works, even if it’s just a trivial byte copy.

For a Copy type, the Clone implementation is always a no‑brainer — it returns *self.

impl Clone for MyType {
    fn clone(&self) -> Self {
        *self   // dereference copies the value (it's Copy, after all)
    }
}

Why the compiler insists on Clone:

Without this rule, a Copy type could leave .clone() undefined. Since Copy guarantees a bit‑perfect duplicate exists, the compiler forces you to expose it through the explicit Clone interface as well. The result is consistency: every Copy type has a working clone().

If you try to derive only Copy, the compiler stops you with an error:

#[derive(Copy)]                // ❌ won't compile
struct Bad;
// error[E0277]: the trait bound `Bad: Clone` is not satisfied

Always pair Copy with Clone — either both derived or both manually implemented.

Implementing the Copy Trait

Derive — the common case

For most stack‑only aggregates, #[derive] generates the boilerplate.

#[derive(Copy, Clone)]
struct Point {
    x: f64,
    y: f64,
}

The derive macro checks that every field is Copy and that no Drop implementation exists. If either condition fails, compilation halts with a clear message.

Manual implementation — when derive is too strict

Deriving Copy adds a Copy bound on generic type parameters. That may be overly restrictive. For example, a struct that only holds a shared reference is Copy regardless of what T is, because &T is always Copy. Derive would force T: Copy, which is unnecessary.

// Manual impl avoids the extra T: Copy bound
struct RefWrapper<'a, T> {
    inner: &'a T,
}
impl<T> Copy for RefWrapper<'_, T> {}
impl<T> Clone for RefWrapper<'_, T> {
    fn clone(&self) -> Self {
        *self
    }
}

Manual implementation is rare; only reach for it when derive produces unwanted bounds. The empty impl Copy for … {} is always valid as long as the type’s memory layout and ownership satisfy the compiler’s checks.

When a Type Can Be Copy

Two rules govern eligibility:

  1. Every field must implement Copy. This includes any nested structs, enums, or arrays. The compiler does a recursive check.
  2. The type must not implement Drop. A custom destructor signals that the type manages a resource beyond its own bytes (heap memory, file handles, locks). Copying the bytes without running the destructor would duplicate that resource, leading to double‑free or other undefined behaviour. The compiler enforces this exclusion.
struct Data {
    x: i32,
    y: bool,
}
// Data can be Copy because i32 and bool are Copy.
struct HasString {
    name: String,
}
// HasString cannot be Copy — String owns a heap buffer.

Copy and Drop are incompatible:

If you implement Drop for a type, any attempt to also implement Copy will cause a compile error. The compiler protects you from types where a simple memcpy would break the ownership model.

Which Standard Types Are Copy

All primitive numeric types (i8i128, u8u128, f32, f64, usize, isize), bool, char, and shared references (&T) implement Copy. Raw pointers *const T and *mut T are also Copy. Compound types like tuples and fixed‑size arrays are Copy if every element is Copy. Function pointers and closure types are Copy when they capture no values or only Copy values.

TypeIs Copy?Explanation
i32YesStack‑only, trivial bytes.
&T (shared ref)YesAlways Copy; multiple shared references are safe.
&mut TNoWould create aliased mutable references, violating borrowing rules.
StringNoOwns a heap buffer — a bitwise copy duplicates the pointer.
Vec<T>NoHeap‑allocated; same problem as String.
Option<i32>YesAll variants’ data are Copy.
Option<String>NoContains a non‑Copy field.
struct Point { x: f64, y: f64 }Yes (if derived)All fields are Copy.

When Should a Type Be Copy?

If your type is a plain bag of data that can live entirely on the stack and has no resource‑management responsibilities, making it Copy improves ergonomics. Users can pass it around without explicit clones, and the code reads more naturally.

There is a trade‑off: once you publish a public type as Copy, removing the implementation is a breaking change. Clients may rely on the implicit copy behaviour. If you suspect the type might one day hold a String, a Vec, or any non‑Copy field, consider leaving Copy off for now.

Think ahead about future fields:

Adding a new field that is not Copy to a previously Copy struct will break every user of the struct. If the struct is part of a public API, this is a semver‑breaking change. Omit Copy until you're confident the set of fields is stable.

A sensible heuristic: start without Copy. Add it later if profiling or real‑world usage shows that explicit .clone() calls become a noticeable pain. For internal, private types, being generous with Copy carries almost no risk.

Common Mistakes

  • Deriving Copy without Clone: the compiler error is explicit, but the temptation to just derive Copy alone is common for beginners.
  • Expecting a container to become Copy because its elements are Copy: a Vec<CopyType> is still not Copy. Only the elements are; the container’s pointer metadata must remain unique.
  • Adding Copy to a type that later receives a String field: the compilation fails, but it’s a gotcha during refactoring if you forget to remove the Copy derive.
  • Manually implementing Clone without realising the type is Copy: calling clone() on a Copy type that hasn't implemented Clone is a compile error. Always implement both together.

Beware of Partial Copies:

A Copy type is duplicated automatically, but only the direct value. If that value holds a non‑Copy field (which is impossible — the compiler prevents it), you'd have a shallow copy problem. This is exactly why the compiler forbids mixing Copy with non‑Copy fields.

Practical Examples

A simple coordinate system

#[derive(Copy, Clone, Debug)]
struct Coords {
    lat: f64,
    lon: f64,
}
fn distance_from_origin(c: Coords) -> f64 {
    (c.lat.powi(2) + c.lon.powi(2)).sqrt()
}
let pos = Coords { lat: 3.0, lon: 4.0 };
let d1 = distance_from_origin(pos);  // pos is copied into the function
let d2 = distance_from_origin(pos);  // still valid
println!("{pos:?}");                 // prints Coords { lat: 3.0, lon: 4.0 }

The function receives a Coords by value; because Coords is Copy, the original pos survives after the call. Without Copy, this code would require passing a reference or cloning explicitly.

Generics with manual Copy implementation

When a generic wrapper stores only a shared reference, a manual Copy impl avoids restricting the inner type unnecessarily.

struct Wrapper<'a, T> {
    data: &'a T,
}
impl<T> Copy for Wrapper<'_, T> {}
impl<T> Clone for Wrapper<'_, T> {
    fn clone(&self) -> Self {
        *self
    }
}
let x = 42;
let w1 = Wrapper { data: &x };
let w2 = w1;                // both w1 and w2 are valid
println!("{}", w1.data);    // 42

This pattern is common in library code that needs to be generic over non‑Copy types but only holds a reference to them.

Summary

Copy is a zero‑cost abstraction that turns move semantics into trivial byte‑duplication for types that live purely on the stack. It’s a marker trait with no methods, yet it radically changes how Rust code looks and feels by removing the need for explicit clones on simple values.

The key takeaways:

  • Copy requires Clone; you derive or implement both together.
  • A type can be Copy only if all its fields are Copy and it does not implement Drop.
  • Copy is appropriate for small, value‑like data that has no resource‑management obligations.
  • Once exposed in a public API, Copy is hard to take away — plan accordingly.

If you understand Copy, you’ll know why i32 works differently from String in assignments. The next deep‑dive, Deref and DerefMut, shows how Rust allows types to behave like references — a completely different kind of implicit behaviour that, unlike Copy, works through explicit trait methods.