Rust Copy and Clone

Assigning one variable to another behaves differently for different types in Rust. Small values such as numbers get duplicated automatically. Heap-based values such as String move to the new owner instead. Two traits control this behavior: Copy and Clone.

Copy Types

A type that implements Copy duplicates its bits on assignment. Both variables stay usable, because each holds its own independent value.

let a = 5;
let b = a;
println!("{a} {b}");
 STACK
 +-------+
 | a = 5 |
 +-------+
 | b = 5 |   (a separate copy)
 +-------+

Types That Implement Copy

  • All integer and float types
  • bool and char
  • Shared references such as &str
  • Tuples and arrays that contain only Copy types

The copy costs almost nothing, because these values are tiny and live on the stack.

Move Behavior

A String owns heap memory. Copying only the handle would leave two owners for one block of letters, and both would try to free it. Rust prevents the problem by moving ownership. The old variable becomes invalid.

let s1 = String::from("tea");
let s2 = s1;
// println!("{s1}");  // error: value moved
 Before move             After move

 s1 --> "tea"            s1 (invalid)
                          s2 --> "tea"

Clone for Deep Copies

The clone method creates a full duplicate, including the heap data. Both variables own separate memory afterward.

let s1 = String::from("tea");
let s2 = s1.clone();
println!("{s1} and {s2}");
 s1 --> "tea"
 s2 --> "tea"    (second block of heap memory)

Clone calls are explicit on purpose. Seeing .clone() in code warns the reader that work and memory are being spent.

Passing Values to Functions

Function calls follow the same rules as assignment. A Copy value duplicates into the parameter. A String moves into the function.

fn show_number(n: i32) {
    println!("{n}");
}

fn show_text(t: String) {
    println!("{t}");
}

fn main() {
    let n = 7;
    show_number(n);
    println!("{n}");      // works

    let t = String::from("hello");
    show_text(t.clone()); // clone keeps t alive
    println!("{t}");      // works
}

Borrowing with a reference avoids both moves and clones. Prefer borrowing when the function only needs to read the data.

Deriving Copy and Clone for Your Types

Custom structs gain these abilities with a derive attribute. Every field must support the trait you derive.

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

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1;
    println!("{:?} {:?}", p1, p2);
}
Point { x: 1, y: 2 } Point { x: 1, y: 2 }

A struct holding a String can derive Clone but never Copy. The String field owns heap memory, which blocks bitwise duplication.

#[derive(Debug, Clone)]
struct User {
    name: String,
    age: u32,
}

Choosing Between Them

QuestionCopyClone
Runs automatically?YesNo, call .clone()
CostTinyCan be large
Handles heap data?NoYes
Original stays usable?YesYes

Performance Advice

Cloning large vectors inside loops slows programs quickly. Profile before optimizing, yet watch for clones added only to silence compiler errors. A borrow or a restructured function often solves the error without extra memory.

Common Mistakes

Calling clone() everywhere to silence compiler errors hides a design problem. Each clone allocates new heap memory. Ask whether a borrow can serve the same purpose.

Using a variable after moving it produces the well-known error message "borrow of moved value". Read the message carefully, because the compiler points to the exact line where ownership left the variable.

Assuming every small type implements Copy causes trouble as well. A struct becomes Copy only after you derive the trait, and only when every field is Copy.

Practice Ideas

  • Write a function that takes a String by value and observe the compiler error when you reuse the original.
  • Change the same function to take &String and confirm the error disappears.
  • Create a Point struct with Copy and a Person struct with Clone only, then compare how each assignment behaves.

Key Points

  • Copy types duplicate automatically on assignment.
  • Heap-owning types move by default.
  • Call clone() for an explicit deep copy.
  • Use #[derive(Clone, Copy)] only when every field allows it.