What Is the Copy Trait and Which Types Implement It?

The Copy trait allows types like integers and booleans to be duplicated by value rather than moved, enabling multiple owners of the same data.

Assignment moves ownership by default

You write let b = a in Python and both variables work fine. You do the same in Rust and the compiler rejects the code, claiming a has been moved. You stare at the screen, confused. Why does Rust treat a simple assignment like a theft? The answer isn't that Rust is being mean. It's that Rust assumes copying is expensive unless you tell it otherwise.

Rust's ownership system enforces that every value has exactly one owner. When you assign a value to a new variable, ownership transfers. The old variable dies. This is a move. The Copy trait changes the rules. If a type implements Copy, assignment creates a duplicate. Both variables own their own independent copy.

Treat Copy as a promise: this type is cheap to duplicate, so the compiler can copy it silently.

Copy flips the script for cheap types

In Rust, almost every value has exactly one owner. When you assign a value to a new variable, ownership transfers. The old variable dies. This is a move. The Copy trait changes the rules. If a type implements Copy, assignment creates a duplicate. Both variables own their own independent copy.

Think of it like writing a phone number on a piece of paper. You can write the same number on ten different papers. No one loses access. The number is cheap to duplicate. Now think of a house key. If you hand the key to a friend, you don't have it anymore. The key represents exclusive access. Rust treats types like keys by default. Copy types are like the phone number. They're cheap to duplicate, so Rust makes a copy instead of moving.

If the cost of duplication is negligible, mark it Copy. If duplication requires work or risks safety, leave it alone.

Minimal example

/// A point in 2D space.
/// Deriving Copy and Clone allows this struct to be duplicated by bitwise copy.
#[derive(Copy, Clone, Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    
    // p1 is copied, not moved. Both p1 and p2 are valid.
    let p2 = p1;
    
    // p1 is still usable because it was copied.
    println!("p1: {:?}", p1);
    
    // p2 owns its own copy of the data.
    println!("p2: {:?}", p2);
}

Assignment copies the bits. Both variables remain valid.

What happens under the hood

Copy is a marker trait. It carries no data and generates no runtime code. It exists purely to change how the compiler interprets your assignments. When the compiler sees let b = a and a implements Copy, it emits machine instructions to copy the bits of a into b. If a does not implement Copy, the compiler emits instructions to transfer the pointer or handle, and then marks a as unusable.

There is zero performance penalty for using Copy. The copy happens at the machine level, exactly as fast as a non-copy move would for the same size data. The only difference is that the original remains valid.

References are always Copy. Every type &T implements Copy. This is why you can pass a reference around freely. The reference itself is just a pointer and metadata. Copying the pointer is safe. The data isn't copied. This distinction matters. Copy applies to the reference, not the data behind it.

Trust the borrow checker. It tracks the references, but the variables holding them can be copied without limit.

The Clone relationship

Copy is a subtrait of Clone. This means every Copy type is also Clone. The Clone trait defines a clone() method that creates a duplicate. For Copy types, calling .clone() is identical to a bitwise copy. You cannot override Clone for a Copy type to do something expensive. The compiler treats .clone() as a copy for these types.

This relationship explains why you always derive both traits together. The compiler requires Clone to be implemented before Copy can be derived. If you write #[derive(Copy)] without Clone, the compiler rejects the code. The convention is to always write #[derive(Copy, Clone)]. The order doesn't matter, but the pair is standard.

Convention aside: #[derive(Copy, Clone)] is the idiom. Never derive Copy alone. The compiler will force your hand anyway, but writing both makes your intent clear to readers.

Realistic usage: function arguments and loops

Copy types shine in function arguments and loops. When you pass a Copy type to a function, the caller copies the value. The function owns its copy. The caller keeps theirs. This avoids borrowing complexity. You don't need to worry about lifetimes or mutable borrows.

/// Configuration for a network request.
/// Copy allows passing this by value without borrowing headaches.
#[derive(Copy, Clone, Debug)]
struct RequestConfig {
    timeout_ms: u32,
    retries: u8,
    verbose: bool,
}

/// Processes a request using the provided config.
/// The config is copied into this function.
fn process_request(config: RequestConfig) {
    println!("Processing with timeout {}ms", config.timeout_ms);
    // config is dropped here, but the caller's copy is untouched.
}

fn main() {
    let config = RequestConfig {
        timeout_ms: 5000,
        retries: 3,
        verbose: false,
    };

    // config is copied. The original remains valid.
    process_request(config);
    
    // config is still usable.
    process_request(config);
    
    // We can even mutate the original and pass a new copy.
    config.verbose = true;
    process_request(config);
}

Passing small structs by value is often cleaner than borrowing. The API is simpler, and the cost is negligible.

Default to ownership for small data. Add Copy to make value semantics the norm.

Pitfalls and compiler errors

You can't derive Copy on a struct that contains a non-Copy field. Types like String, Vec, and Box manage heap memory and implement Drop. If you try to derive Copy on a struct containing these, the compiler rejects you with E0204 (the trait Copy cannot be implemented for this type). The compiler knows that copying the bits of a String would duplicate the pointer, leading to a double-free when both copies drop.

Copy is forbidden for any type that implements Drop. If your type cleans up resources, it can't be copied blindly. The compiler enforces this to prevent double-frees. If you try to implement Copy on a type with a Drop implementation, you get E0184 (the trait Copy cannot be implemented for this type). The error message explains that Drop and Copy are incompatible.

Never implement Drop and Copy on the same type. The compiler will block you, and for good reason.

Performance and size conventions

Copy is a semantic guarantee, not a performance guarantee. The trait says "bitwise copy is safe." It does not say "bitwise copy is fast." You can define a struct with a thousand u8 fields and derive Copy. The compiler will allow it. Copying that struct will take time. The convention is to only mark types as Copy when the copy is cheap. Usually, this means the type fits in a CPU register or two.

If you copy a large struct repeatedly in a hot loop, you might be wasting cycles. Profile before you panic. But the trait itself doesn't enforce size. The developer guarantees speed by only marking small things Copy.

If your struct is large but contains only primitive data, consider whether you really need to copy it. Passing a reference might be faster. The type system lets you choose.

Profile your hot loops. Copy is safe, but it's not free if the data is big.

Decision matrix

Use Copy for primitive types like integers, floats, booleans, and characters. Use Copy for structs composed entirely of Copy fields, like a Point or Color. Use Copy when you want to pass values by value without borrowing complexity, and the data is small enough that copying is cheap. Reach for owned types without Copy when your data manages heap memory, like String, Vec, or Box. Reach for owned types without Copy when your type implements Drop and must run cleanup logic. Reach for references when you need to share large data without copying and don't need multiple owners.

Default to ownership. Add Copy only when the data is cheap and you want the convenience of value semantics.

Where to go next