DEV Community

Amrit Singh
Amrit Singh

Posted on

Mastering `Copy` and `Clone` traits in Rust

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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 Copy operation is just a memcpy. 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.
Enter fullscreen mode Exit fullscreen mode

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 Copy must also implement Clone.

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 { ... }
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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(),
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Using #[derive(Copy, Clone)] is equivalent to this manual implementation for types that qualify.

When to Use Copy vs. Clone

  • Use Copy for small, simple types that can be trivially duplicated. These are types that "feel like" values, such as coordinates, colors, or simple identifiers. Implementing Copy makes them more ergonomic to use, as they can be passed around freely without worrying about ownership moves.
  • Use Clone for types that own resources or require more complex duplication logic. This includes types that own heap memory (String, Vec, Box) or contain other non-Copy data. Calling .clone() signals that a potentially significant operation is happening.
  • Prefer &T (a borrow) over clone() 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)