Skip to content
beginner Phase 2 · Ownership and Move Semantics

Copy and clone Traits

Mark simple types as implicitly copyable and opt into explicit deep clones.

30m
0 problems
Topic Progress 0%

Copy Types Duplicate Instead of Moving — and Only a Few Qualify

You have seen non-Copy values move on assignment. Copy is the opt-in that makes assignment duplicate the bits instead, so both bindings stay valid:

fn main() {
    let a = 42_i32;
    let b = a;              // bit copy, NOT a move
    println!("{} {}", a, b);   // both fine: 42 42
}

i32 is Copy, so this compiles where String would not. The trait is marker-like — its presence changes assignment behavior — and there is a strict rule for when it can apply:

A type is Copy if all its fields are Copy.

#[derive(Clone, Copy)]
struct Point { x: f64, y: f64 }   // all fields Copy → Point can be Copy

#[derive(Clone, Copy)]
struct Pair { id: u64, p: Point } // structural: composed of Copy fields

struct Label { text: String }     // String is not Copy → Label cannot be Copy

Which concrete types are Copy? The primitives (i8–i128, u8–u128, f32/f64, bool, char), tuples and arrays/structs made entirely of Copy fields, and — this surprises people — shared references: &T is Copy for every T, which is exactly why passing &s around never moves the borrow. What is emphatically not Copy: String, Vec<T>, Box<T>, File — anything owning heap memory or an OS resource. One non-Copy field disqualifies the whole type.

Two mechanics trip up everyone once. First, Copy requires Clone: #[derive(Copy)] alone fails with a compiler error telling you to add Clone, because a bitwise copy must be expressible as an explicit clone too. The full opt-in is the pair #[derive(Clone, Copy)]. Second, Copy has no user-written methods — you never impl Copy with a body; the compiler provides the bit-copy semantics directly. The method .copy() does not exist; the operation is invisible, triggered by assignment, argument passing, or any binding of the value.

The semantic consequence matters in real code: a Copy value passed to a function is duplicated, so the caller keeps using it without writing &:

fn perimeter(w: f64, h: f64) -> f64 { 2.0 * (w + h) }

fn main() {
    let width = 10.0;
    let p = perimeter(width, 5.0);   // width copied into the parameter — no move
    println!("{} {}", width, p);    // width still usable
}

This is the deep reason Copy types feel frictionless — there is nothing to track, because duplication creates no second owner of any resource; each copy is independent and self-contained.

Copy is decided structurally all the way down, including through enums and generic containers:

#[derive(Clone, Copy)]
struct Reading<'a> { sensor: &'a str, value: f64 }   // &str field is Copy → Reading is

#[derive(Clone, Copy)]
enum Signal { Off, On }                              // no payloads → Copy

let ok: Option<i32> = Some(3);   // Option<i32> is Copy: payload Copy → enum Copy
let bad: Option<String> = None;  // Option<String> is NOT Copy

Option<T> inherits from its payload, which is why functions return Option<i32> by value without a thought and Option<String> moves. The failing case produces its own instructive diagnostic when you try anyway:

error[E0277]: the trait bound `String: Copy` is not satisfied
 --> src/main.rs:3:10
  |
3 | #[derive(Clone, Copy)]
  |          ----- required by a bound in this derive macro
  |
  = note: `String` is not `Copy`

Read that as "you asked for Copy, and one field said no" — the derive points at the bound, and the fix is to drop Copy (keep Clone) or drop the offending field.

One caution to keep Copy honest: Copy is not the same as free. Duplicating [u64; 128] is a 1 KB memcpy, and a struct of arrays gets larger the same way. Rust's own guidance is to reserve Copy for types "usually no larger than the size of two machine words" (pointers plus a little), because at that size the compiler's duplication costs nothing to schedule and no one needs to think about it. Past that, explicit .clone() makes the cost visible — the same argument that keeps Clone explicit in the first place. Practical consequence for signatures: x: i32 and x: &i32 are both fine, but idiomatic Rust passes small scalars by value and larger plain data by reference, exactly to keep the copying implicit-and-tiny or explicit-and-honest.

Clone Is an Explicit, Visible, Potentially Expensive Copy

Clone is the general-purpose trait for producing an independent duplicate, and its defining property is that it is explicit at the call site:

let a = String::from("hello");
let b = a.clone();      // deep copy: new heap buffer, two independent Strings
println!("{} {}", a, b);   // both valid — unlike a bare move

The .clone() call is a cost marker. Reviewers see it, the compiler does not insert it silently, and anyone reading performance-sensitive code can grep for it. Contrast this with garbage-collected languages where duplication is invisible and heap churn is discovered later in a profiler.

Deriving Clone is mechanical:

#[derive(Clone)]
struct User {
    id: u64,
    name: String,        // String: Clone → deep-copies the buffer
    tags: Vec<String>,   // Clone recurses: each tag String is cloned
}

let u1 = User { id: 1, name: String::from("ada"), tags: vec![String::from("admin")] };
let u2 = u1.clone();     // fully independent: name and every tag are fresh allocations

The derive walks every field and calls its clone(), so the result shares no heap state with the original. That deep-by-default behavior is the safe contract — a "shallow clone" bug (two owners of one buffer) is impossible from Clone alone.

When the default derive cannot express the copy, you write the impl:

impl Clone for Cursor {
    fn clone(&self) -> Self {
        Cursor {
            position: self.position,
            buffer: self.buffer.clone(),      // deep-copy the owned Vec
        }
    }
}

The signature fn clone(&self) -> Self takes a shared borrow and returns a new owned value — notably it does not consume the original, which is what makes a.clone() usable while a remains live. Clone's supertrait relationship closes the loop: trait Clone: Sized provides clone, and implementing Copy types also get Clone automatically via the same derive pair.

One design pattern built entirely on Clone's explicitness is copy-on-write (the Cow<str> type): store a &str (cheap, borrowed), and only call .to_owned() / clone when you must mutate. The clone becomes the visible boundary where shared data turns private. The counterpoint to hold in interviews: .clone() in a hot loop over large structures is a real performance smell — cloning a Vec<Vec<String>> per iteration allocates per element per pass. The disciplined answer is usually a reference (&T) or restructuring, with clone reserved for genuinely-needed duplication at cold boundaries.

The word "clone" hides wildly different costs, and knowing the ladder is the point:

Type What clone() does Cost
i32, Point bit copy nanoseconds, no allocation
String, Vec<T> allocate + copy bytes O(n)
Deep Vec<Vec<String>> recursive, one alloc per node O(n) allocations
Arc<T> atomic reference-count increment one relaxed atomic add
Rc<T> non-atomic count increment one increment

That last row is why Arc::clone() in a hot loop is a non-issue while Vec::clone() in the same loop is a bug — the call site looks identical; the semantics are not. When you see .clone() in performance-critical code, the question to ask is not "why clone?" but "which type, and what does its clone cost?"

Two refinements that matter once you are writing generic code. First, cloning into an existing value can reuse its allocation instead of discarding it:

let mut a = String::with_capacity(1024);
a.clone_from(&b);       // clears and REFILLS a's buffer if capacity suffices — no new alloc
let c = a.clone();      // plain clone always allocates fresh

clone_from is the default impl falling back to clone, but String and Vec override it to recycle capacity — measurable when cloning into a long-lived buffer repeatedly. Second, derived clones on generic types come with an implied bound: #[derive(Clone)] struct Wrapper<T> { inner: T } generates impl<T: Clone> Clone for Wrapper<T>, so a Wrapper<Box<dyn Trait>> has no Clone unless Box<dyn Trait> does. That missing-bound error (the trait bound ... is not satisfied on Wrapper<T>: Clone) is one of the most common clone-related diagnostics, and the fix is either T: Clone in your own bounds or a hand-written clone() that skips what cannot be cloned.

Why Copy and Drop Can Never Coexist (E0184)

Here is a rule that looks arbitrary until you see the reasoning — and then becomes unforgettable:

A type cannot implement both Copy and Drop.

Try it and the compiler stops you:

struct FileHandle { fd: i32 }

impl Drop for FileHandle {
    fn drop(&mut self) { /* close(fd) */ }
}

impl Clone for FileHandle { fn clone(&self) -> Self { FileHandle { fd: self.fd } } }
impl Copy for FileHandle {}   // error[E0184]: cannot have both `impl Copy`
                              //              and a manual `Drop` impl

The reasoning is the double-free argument from the ownership rules, applied to duplication. Copy duplicates values bit-by-bit on assignment, with no user code invoked. If a Drop type were also Copy, every duplicate would carry the full resource — and every duplicate's destructor would run at its own scope end. One file descriptor, three owners, three close() calls. One heap buffer, N owners, N frees. Copy's invisible duplication and Drop's cleanup-once guarantee are logically incompatible, so Rust forbids the combination outright rather than letting you build the bug.

This produces a clean two-category map of the type world:

Category Trait Examples Assignment
Plain data Copy (+Clone) integers, bool, char, Point{f64,f64} bit-duplicated, both valid
Resources neither (or Clone only) String, Vec<T>, File, MutexGuard moved (or explicit clone)

The practical decision procedure this enables: if the type owns nothing that must be released exactly once, it can be Copy; if it does, it must never be — and Clone is available for either kind when explicit duplication is genuinely wanted. String is Clone but not Copy (duplication is legal but expensive and explicit); i32 is both (duplication is free and invisible); File is neither (duplication is nonsense — you want a second open, not a copied descriptor number).

For an interview, the sequence to be ready for: "When would you derive Copy vs Clone?" — derive Copy for small plain-data types (it makes APIs frictionless and the compiler enforces the safety limit); derive Clone for anything with owned data that callers may legitimately want duplicated; implement neither when duplication is meaningless or dangerous, and say out loud the E0184 double-free argument that enforces the boundary. That is the whole trait pair, understood.

Draw the boundary carefully, because a common misreading is "a type that implements Drop cannot implement any duplication trait." Clone and Drop coexist everywhere — String, Vec<T>, and Box<T> all implement both, because a Clone call is explicit: you asked for a second resource, you got a second resource, and each one drops exactly once. The prohibition is specific:

Combination Legal? Why
Clone + Drop yes duplication is explicit and requested; each copy owns its own resource
Copy + Drop no — E0184 duplication would be invisible and unbounded; N copies, N destructor runs
Copy + Clone required Clone is a supertrait of Copy

The compiler's own words, which you should be able to produce from memory:

error[E0184]: cannot have both `impl Copy` and a manual `Drop` impl

There is no configuration that softens it, no attribute that allows it — the type system refuses to represent a value that can be duplicated without limit and must also be cleaned up exactly once. That refusal is the whole safety argument compressed into one lint-level check.

You can even observe the distinction at runtime when you need to reason about it: std::mem::needs_drop::<T>() returns false for i32 (its drop is a no-op the compiler elides) and true for String. Types for which it is false are the natural Copy candidates — nothing to run at scope end, so duplication is pure bits. If there is nothing to clean up, there is nothing to protect, and Copy is simply the name Rust gives to "assignment means duplication." State that sentence and the three topics you have just read — ownership, moves, and this trait pair — collapse into one rule.

Quiz

1. Why must `#[derive(Copy)]` always be paired with `#[derive(Clone)]`, and what does the pair change about assignment?

Question 1 options

2. What does `#[derive(Clone)]` on a struct with a String and Vec<String> fields actually duplicate?

Question 2 options

3. Why does the compiler reject a type that implements both Copy and Drop (E0184)?

Question 3 options

4. Which types can be marked Copy?

Question 4 options

Flashcards

Question

What is the structural rule for a type to be Copy, and how does assignment differ for Copy vs non-Copy?

Answer

Every field must be Copy (and the type must not impl Drop). Assignment of a Copy value bit-duplicates it — both bindings stay valid, nothing moves. Assignment of a non-Copy value MOVES ownership and invalidates the source (E0382 on reuse). Copy requires Clone as a supertrait.

Question

How deep is a derived clone(), and what does the signature look like?

Answer

Deep: #[derive(Clone)] generates clone() that calls clone() on every field recursively, so owned heap data (String, Vec) is freshly allocated — the two values share nothing. Signature: fn clone(&self) -> Self — it BORROWS the original (never consumes it) and returns a new independent value.

Question

Why is Copy forbidden for types that impl Drop? Quote the error.

Answer

E0184: 'cannot have both `impl Copy` and a manual `Drop` impl'. Copy duplicates values invisibly (no user code); each duplicate would be a full owner and run the destructor at its own scope end — N copies of a File/heap owner means N close() calls / frees. Cleanup-exactly-once and invisible duplication are incompatible.

Question

Copy vs Clone vs neither — a decision rule with examples.

Answer

Copy (+Clone): small plain data with nothing to release — i32, f64, bool, char, Point{f64,f64}, &T; frictionless, free duplication. Clone only: owned data where explicit duplication is legitimate — String, Vec<T>; duplication costs an allocation but is safe. Neither: duplication is meaningless or dangerous — File, MutexGuard; you want a fresh open/lock, not a copied handle.

Question

Why is &T Copy, and what practical difference does that make?

Answer

A reference is one pointer — plain data with nothing to free — so &T is Copy: binding or passing it duplicates the pointer and both copies remain valid. Practical effect: functions taking &T let callers pass the reference without thinking about moves; the underlying value's ownership is untouched by any number of reference copies.

Revision Notes

Key Takeaways

  • 1. Copy is structural — every field must be Copy and the type must not impl Drop; assignment duplicates bits so both bindings stay valid
  • 2. Clone is explicit, deep by default, and borrows its source (&self -> Self), making duplication a visible, greppable cost at the call site
  • 3. E0184 forbids Copy+Drop: invisible duplication would create N owners of one resource and run the destructor N times — the double-free bug, blocked statically
  • 4. Three-way split: plain data derives Clone+Copy, owned data derives Clone only (moves + explicit clones), resources implement neither (moves only)
  • 5. &T is Copy — copying a reference is copying a pointer, which is why borrowing APIs are frictionless for callers

Interview Tips

  • • "Copy vs Clone?" — Copy is invisible compiler-provided bit duplication for resource-free types; Clone is an explicit user-visible deep copy for anything; note Copy: Clone as a supertrait and the derive pair
  • • "Can a File handle be Copy?" — no, and give the full chain: it impls Drop (close on scope end), Copy+Drop is E0184, duplication would double-close; the correct duplicate is a fresh open() or an explicit Arc sharing
  • • "Performance implication of clone()?" — allocation + element-wise copy (O(n)); in hot loops prefer references; mention copy-on-write (Cow) as the pattern that clones only when mutation demands privacy
  • • "Why are references Copy?" — they are one pointer with nothing to free, so duplication is safe and free; this is why &T parameters are the frictionless default in API design

Cheat Sheet

Copy and Clone Traits

Copy: invisible bit-duplication on assignment; no methods; requires all fields Copy; requires Clone (derive both: #[derive(Clone, Copy)]).

  • Copy: primitives, tuples/arrays/structs of Copy fields, &T
  • NOT Copy (one field poisons the struct): String, Vec, Box, File

Clone: explicit .clone() deep copy — fn clone(&self) -> Self borrows the source; derive walks every field recursively (fresh heap buffers, zero sharing). Visible cost marker — grep it in hot paths.

The hard rule:

impl Copy for FileHandle {}  // error[E0184]: cannot have both `impl Copy`
                             //              and a manual `Drop` impl

Copy duplicates owners invisibly; Drop frees once per owner → N copies = N frees. Forbid the combination statically.

Decision table:

Kind Traits Assign/Pass
Plain data (i32, Point) Clone + Copy free duplicate
Owned data (String, Vec) Clone only moves; clone() duplicates explicitly
Resources (File, guard) neither moves only; duplication is wrong

Pattern: copy-on-write (Cow<str>) — borrow by default, clone only at the mutation boundary.

Continue Learning