Ownership

Move vs. Copy vs. Clone

Rust provides three distinct mechanisms for transferring or duplicating values across bindings and function boundaries: Move, Copy, and Clone. Understanding how they differ in syntax, runtime cost, and ownership transfer is central to writing idiomatic Rust.

Side-by-Side Comparison

Dimension Move Copy (std::marker::Copy) Clone (std::clone::Clone)
Trigger syntax Implicit (let b = a; on non-Copy types) Implicit (let b = a; on Copy types) Explicit method call (let b = a.clone();)
Source variable after operation Invalidated (cannot be read or dropped) Remains valid and independent Remains valid and independent
What happens in memory Shallow bitwise copy of stack header (memcpy) Shallow bitwise copy of stack value (memcpy) Arbitrary code (often deep heap allocation + copy)
Runtime cost Always $O(1)$ (few stack bytes) Always $O(1)$ (few stack bytes) Variable ($O(1)$ to $O(n)$ heap allocation)
Can implement Drop? Yes No (E0184—mutually exclusive with Drop) Yes
1. Move (`let b = a;` for String):      `a` (invalidated)      `b` ──> [Heap: "rust"] (1 buffer)
2. Copy (`let b = a;` for i32):         `a` = 42               `b` = 42               (2 stack ints)
3. Clone (`let b = a.clone();` String): `a` ──> [Heap: "rust"] `b` ──> [Heap: "rust"] (2 heap buffers!)

Demonstrating All Three Together

#[derive(Debug, Clone, Copy)]
struct Point(i32, i32); // Implements both `Copy` and `Clone`

fn main() {
    // 1. Copy: implicit bitwise duplication on stack
    let p1 = Point(3, 4);
    let p2 = p1;
    println!("Copy:  p1 = {p1:?}, p2 = {p2:?}");

    // 2. Move: implicit ownership transfer; `s1` is invalidated
    let s1 = String::from("moved text");
    let s2 = s1;
    println!("Move:  s2 = '{s2}'");

    // 3. Clone: explicit deep heap duplication; both `s2` and `s3` remain valid
    let s3 = s2.clone();
    println!("Clone: s2 = '{s2}', s3 = '{s3}'");
}
Copy:  p1 = Point(3, 4), p2 = Point(3, 4)
Move:  s2 = 'moved text'
Clone: s2 = 'moved text', s3 = 'moved text'

Choosing the Right Mechanism

  • Default to borrowing (&T) or moving (T) whenever a function or variable does not need a second independent heap copy.
  • Derive Copy (#[derive(Clone, Copy)]) on small stack-only value types so they behave like primitive integers.
  • Call .clone() explicitly when you intentionally need two independent owners of heap data (or use Rc<T> / Arc<T> when .clone() should cheaply share read-only heap data via reference counting).