Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1.1 · Clone, Copy, and Drop: The Lifecycle Trio

Domain 1 — Ownership Mechanics in the Standard Library Duration: ~15 minutes Library components: std::clone::Clone, std::marker::Copy, std::ops::Drop, clone_from

Introduction

Each value in Rust has exactly one owner. When that owner goes out of scope, Rust destroys the value. Three traits of the standard library control how Rust duplicates and destroys values: Copy, Clone, and Drop. The three traits interact, and the interactions can cause mistakes, also for experienced developers.

This tutorial shows:

  • When the compiler makes implicit copies, and when you must clone explicitly.
  • The rule that Copy and Drop are mutually exclusive.
  • How clone_from prevents unnecessary allocations.
  • The performance cost of Clone on collection types in real programs.

This tutorial assumes that you know ownership and borrowing. It examines the standard library traits that extend those concepts.

Figure: Copy, Clone, or Move?

Copy: Implicit Bitwise Duplication

Copy is a marker trait: it has no methods. When a type implements Copy, the compiler can duplicate a value with a bitwise memcpy. The compiler does this when you assign the value, pass it to a function, or return it. The copy is implicit. You never call a .copy() method.

Which types are Copy?

These types are Copy:

  • All primitive scalar types (i32, f64, bool, char).
  • References (&T).
  • Raw pointers.
  • Tuples and arrays that contain only Copy types.

String, Vec<T>, and Box<T> are not Copy. No type that owns heap memory is Copy.

Use case: Primitive values survive assignment

When you assign an integer to a second variable, the two variables stay usable. The reason is that i32 is Copy. Without that trait, the assignment would be a move.

fn main() {
    let x: i32 = 42;
    let y = x; // implicit copy: x stays valid
    println!("x = {x}, y = {y}");
    assert_eq!(x, 42);
    assert_eq!(y, 42);

    // A tuple of Copy types is also Copy.
    let point = (3.0_f64, 4.0_f64);
    let other = point; // implicit copy: point stays valid
    println!("point = {point:?}, other = {other:?}");
    assert_eq!(point, other);

    // String is not Copy: the assignment moves ownership.
    let s1 = String::from("hello");
    let s2 = s1; // move: s1 is not valid after this line
    // println!("{s1}");  // compile error E0382: borrow of moved value: `s1`
    println!("s2 = {s2}");

    // A struct can derive Copy when all its fields are Copy.
    // Clone is a supertrait of Copy, so the derive list has the two traits.
    #[derive(Debug, Clone, Copy)]
    struct Pixel { r: u8, g: u8, b: u8 }

    let red = Pixel { r: 255, g: 0, b: 0 };
    let also_red = red; // implicit copy: red stays valid
    println!("red = {red:?}, also_red = {also_red:?}");
    assert_eq!(red.r, also_red.r);

    // A function call copies a Copy argument.
    fn double(n: i32) -> i32 { n * 2 }

    let val = 10;
    let doubled = double(val); // the function gets a copy of val
    println!("val = {val}, doubled = {doubled}"); // val is still usable
    assert_eq!(val, 10);
}

01_01_copy_semantics.rs prints:

x = 42, y = 42
point = (3.0, 4.0), other = (3.0, 4.0)
s2 = hello
red = Pixel { r: 255, g: 0, b: 0 }, also_red = Pixel { r: 255, g: 0, b: 0 }
val = 10, doubled = 20

The important point: Copy is an opt-in promise to the compiler that a bitwise duplicate of your type is semantically correct. The compiler accepts the promise. Thus it does not call custom code. It only copies the bytes.

Clone: Explicit, Potentially Expensive Duplication

Clone is a regular trait with a method: fn clone(&self) -> Self. A call to clone() is always explicit, which is different from Copy. The implementation can do any operation. For example, it can allocate heap memory, copy nested structures, or increment reference counts.

Each Copy type is also Clone, because Clone is a supertrait of Copy. But not all Clone types are Copy.

Use case: Duplicating heap-owning types

String, Vec<T>, and each struct that contains them need an explicit .clone() call to make a second independent copy.

fn main() {
    // String is Clone but NOT Copy.
    let original = String::from("deep sea");
    let cloned = original.clone(); // allocates a new heap buffer
    println!("original = {original:?}");
    println!("cloned   = {cloned:?}");
    assert_eq!(original, cloned);

    // The two strings are independent: a change to one does not change the other.
    let mut cloned = cloned; // moves the clone into a mutable binding
    cloned.push_str(" diving");
    println!("after mutation:");
    println!("  original = {original:?}");
    println!("  cloned   = {cloned:?}");
    assert_ne!(original, cloned);

    // A struct can derive Clone when all its fields are Clone.
    #[derive(Debug, Clone)]
    struct Document {
        title: String,
        pages: Vec<String>,
    }

    let doc = Document {
        title: String::from("Rust Handbook"),
        pages: vec![String::from("Chapter 1"), String::from("Chapter 2")],
    };

    let doc2 = doc.clone(); // deep clone: duplicates the title AND each page
    println!("\ndoc  = {doc:?}");
    println!("doc2 = {doc2:?}");
    assert_eq!(doc.title, doc2.title);
}

01_02_clone_vs_copy.rs prints these lines for the code above:

original = "deep sea"
cloned   = "deep sea"
after mutation:
  original = "deep sea"
  cloned   = "deep sea diving"

doc  = Document { title: "Rust Handbook", pages: ["Chapter 1", "Chapter 2"] }
doc2 = Document { title: "Rust Handbook", pages: ["Chapter 1", "Chapter 2"] }

The derived Clone does not show an important cost. A clone of a Document does 2 + N allocations. One is for the title, one is for the Vec buffer, and N are for the page strings. The cost increases linearly with the quantity of data. A later section of this tutorial measures this cost.

The Copy/Drop Mutual Exclusion

Drop is the trait that gives a type a destructor: fn drop(&mut self). The compiler calls the destructor automatically when a value goes out of scope.

The rule is: a type cannot implement Copy and Drop together. The compiler enforces this rule.

The reason is that Copy means "a copy of the bytes is a valid duplicate". If a type has a destructor, a bitwise copy would make two owners of the same resource. When the two owners go out of scope, the destructor runs two times. The result is a double free, a double close, or a second run of some other resource cleanup.

Figure: Copy and Drop Are Mutually Exclusive

Use case: Why file handles are not Copy

fn main() {
    // A type that prints a line when Rust drops it.
    #[derive(Debug)]
    struct Noisy { id: u32 }

    impl Drop for Noisy {
        fn drop(&mut self) {
            println!("  Dropping Noisy(id={})", self.id);
        }
    }

    // Noisy implements Drop, so it CANNOT implement Copy.
    // impl Copy for Noisy {}  // compile error E0184: the type has a destructor

    println!("Creating two Noisy values:");
    {
        let a = Noisy { id: 1 };
        let b = Noisy { id: 2 };
        println!("  a = {a:?}, b = {b:?}");
        // The scope ends here: b drops first (reverse declaration order), then a.
    }
    println!();

    // Contrast: a Copy type has no destructor.
    #[derive(Debug, Clone, Copy)]
    struct Point { x: f32, y: f32 }

    let p1 = Point { x: 1.0, y: 2.0 };
    let p2 = p1; // bitwise copy: no destructor runs
    let p3 = p1; // a second copy: p1 is still valid
    println!("p1 = {p1:?}");
    println!("p2 = {p2:?}");
    println!("p3 = {p3:?}");
}

01_03_copy_drop_exclusion.rs prints these lines for the code above:

Creating two Noisy values:
  a = Noisy { id: 1 }, b = Noisy { id: 2 }
  Dropping Noisy(id=2)
  Dropping Noisy(id=1)

p1 = Point { x: 1.0, y: 2.0 }
p2 = Point { x: 1.0, y: 2.0 }
p3 = Point { x: 1.0, y: 2.0 }

This mutual exclusion is not an arbitrary rule. It is a direct result of memory safety. If std::fs::File were Copy, an assignment would silently make two file handles for the same OS descriptor. If the program then closed the two handles, the behavior would be undefined.

Drop Order

It is also important to know when destructors run and in what order.

There are three rules:

  1. Local variables drop in reverse declaration order: the last variable drops first.
  2. Struct fields drop in declaration order: the first field drops first.
  3. Vec elements drop in front-to-back order: index 0 drops first.

To destroy a value before the end of its scope, call std::mem::drop(value).

Use case: Releasing a lock before doing more work

fn main() {
    // A type that prints its name when Rust drops it.
    #[derive(Debug)]
    struct Named(&'static str);

    impl Drop for Named {
        fn drop(&mut self) {
            println!("  Dropped: {}", self.0);
        }
    }

    // Local variables: reverse declaration order.
    println!("=== Local variable drop order ===");
    {
        let _first = Named("first");
        let _second = Named("second");
        let _third = Named("third");
        println!("  All three are alive");
        // The scope ends here: the drop order is third, second, first.
    }

    // Manual drop before the end of the scope.
    println!("\n=== Early drop with std::mem::drop ===");
    {
        let guard = Named("mutex_guard"); // represents a lock guard
        let _resource = Named("resource");
        println!("  Both alive — doing work...");
        drop(guard); // moves guard into drop(), which destroys it immediately
        println!("  Guard released — resource still alive");
        // The scope ends here: only _resource drops.
    }

    // Vec elements: front to back.
    println!("\n=== Vec element drop order ===");
    {
        let _v = vec![Named("vec[0]"), Named("vec[1]"), Named("vec[2]")];
        println!("  Vec is alive");
        // The scope ends here: the drop order is vec[0], vec[1], vec[2].
    }
}

01_06_drop_order.rs prints these lines for the code above:

=== Local variable drop order ===
  All three are alive
  Dropped: third
  Dropped: second
  Dropped: first

=== Early drop with std::mem::drop ===
  Both alive — doing work...
  Dropped: mutex_guard
  Guard released — resource still alive
  Dropped: resource

=== Vec element drop order ===
  Vec is alive
  Dropped: vec[0]
  Dropped: vec[1]
  Dropped: vec[2]

Figure: Drop Order — Locals vs. Struct Fields vs. Vec Elements

clone_from: Reusing Existing Allocations

Most developers know .clone(). Fewer developers know .clone_from(). Its signature is:

// A provided method of the Clone trait. `self` is the destination.
fn clone_from(&mut self, source: &Self) { ... }

The default implementation is *self = source.clone(). It discards the old value and makes a new allocation. But String and Vec override the method to use the existing heap buffer again when the buffer is sufficiently large. This prevents one deallocation and one new allocation.

Use case: A processing loop that reuses a buffer each iteration

fn main() {
    // The destination: a string with a heap buffer of at least 100 bytes.
    let mut buffer = String::with_capacity(100);
    buffer.push_str("old content that allocated a nice big buffer");

    let ptr_before = buffer.as_ptr(); // the address of the heap buffer
    let cap_before = buffer.capacity();
    println!("Before clone_from:");
    println!("  buffer = {buffer:?}");
    println!("  capacity = {cap_before}");

    let source = String::from("new");
    buffer.clone_from(&source); // copies "new" into the existing buffer

    let ptr_after = buffer.as_ptr();
    let cap_after = buffer.capacity();
    println!("\nAfter clone_from:");
    println!("  buffer = {buffer:?}");
    println!("  capacity = {cap_after}");

    assert_eq!(buffer, "new");
    // The pointer is the same: clone_from used the same allocation.
    assert_eq!(ptr_before, ptr_after);
    // The capacity is the same: clone_from did not shrink the buffer.
    assert_eq!(cap_before, cap_after);

    // A processing loop that uses one buffer for all the messages.
    println!("\n--- Simulated processing loop ---");
    let incoming_messages = vec![
        String::from("msg-alpha"),
        String::from("msg-beta-longer"),
        String::from("msg-gamma"),
    ];

    let mut work_buffer = String::with_capacity(64);
    for msg in &incoming_messages {
        // Each message fits in the 64 bytes, so this call does not allocate.
        work_buffer.clone_from(msg);
        println!("  Processing: {work_buffer:?} (cap={})", work_buffer.capacity());
    }
}

01_04_clone_from.rs prints these lines for the code above:

Before clone_from:
  buffer = "old content that allocated a nice big buffer"
  capacity = 100

After clone_from:
  buffer = "new"
  capacity = 100

--- Simulated processing loop ---
  Processing: "msg-alpha" (cap=64)
  Processing: "msg-beta-longer" (cap=64)
  Processing: "msg-gamma" (cap=64)

The capacity stays at 64 in all three iterations. Without clone_from, each iteration would allocate a new buffer and deallocate the old buffer.

The general rule: when you overwrite a variable that already has an allocation, prefer dest.clone_from(&src) to dest = src.clone().

Performance Implications of Clone on Collections

A clone has a cost, and the cost depends very much on the contents of the collection.

  • Vec<i32>.clone() does one memcpy of the backing buffer. It is fast.
  • Vec<String>.clone() clones each String in the vector, and each clone allocates. The cost is O(n) in the element count and in the total string length.
  • HashMap<String, Vec<u8>>.clone() clones each key and each value. It is expensive.

Use case: Measuring and avoiding clone costs

use std::time::Instant;

fn main() {
    // 100,000 strings: each string has its own heap buffer.
    let big_vec: Vec<String> = (0..100_000).map(|i| format!("element-{i:06}")).collect();

    let start = Instant::now();
    let cloned_vec = big_vec.clone(); // clones each String: one allocation for each element
    let clone_time = start.elapsed(); // the Duration of the clone
    println!(
        "Cloning Vec<String> with {} elements: {:?}",
        big_vec.len(),
        clone_time
    );
    assert_eq!(big_vec.len(), cloned_vec.len());

    // 100,000 integers: all the data is in one buffer.
    let int_vec: Vec<i32> = (0..100_000).collect();

    let start = Instant::now();
    let cloned_ints = int_vec.clone(); // one memcpy of the backing buffer
    let int_clone_time = start.elapsed();
    println!(
        "Cloning Vec<i32> with {} elements:    {:?}",
        int_vec.len(),
        int_clone_time
    );
    assert_eq!(int_vec.len(), cloned_ints.len());

    // `.max(1)` prevents a division by zero if the measured time is 0 ns.
    let ratio = clone_time.as_nanos() as f64 / int_clone_time.as_nanos().max(1) as f64;
    println!("\nVec<String> clone was roughly {ratio:.0}x slower than Vec<i32> clone");
}

01_05_clone_performance.rs prints these lines for the code above. The times and the ratio are different in each run:

Cloning Vec<String> with 100000 elements: 1.46925ms   # (varies)
Cloning Vec<i32> with 100000 elements:    30.75µs   # (varies)

Vec<String> clone was roughly 48x slower than Vec<i32> clone   # (varies)

(The exact numbers vary by machine, but the order-of-magnitude difference is consistent.)

Strategies to avoid unnecessary clones

  1. Pass a slice (&[T]), and do not clone the collection. If you only read the data, borrow it.
  2. Clone one element, not the full collection. If you need one item, clone that item.
  3. Consume the collection with into_iter(). If you do not need the original collection after this point, move the elements and do not clone them.
  4. Use clone_from when you overwrite a value that has an allocation (see the clone_from section).

Summary

TraitMethodImplicit?CostRequires
CopyNone (the compiler inserts a memcpy)YesLow (bitwise copy)All fields must be Copy, and the type has no Drop impl
Clone.clone()No (explicit call)Arbitrary (may allocate)All fields must be Clone
Drop.drop() (the compiler calls it)YesArbitrary (cleanup code)The type cannot be Copy

The three traits make one system:

  • Copy means that duplication is trivially safe: the compiler only copies the bytes.
  • Clone means that duplication needs work: you call the clone method.
  • Drop means that destruction needs work: the compiler calls the destructor.
  • Copy together with Drop is not permitted, because trivial duplication and non-trivial destruction are incompatible.

clone_from is the performance tool for Clone types. It lets you use an existing allocation again when you overwrite a value.

Code Examples

FileDescription
01_01_copy_semantics.rsImplicit copies of primitives, tuples, and custom structs
01_02_clone_vs_copy.rsExplicit clones of String, Vec, and nested structs
01_03_copy_drop_exclusion.rsWhy a type cannot implement Copy and Drop together
01_04_clone_from.rsHow clone_from uses an existing allocation again
01_05_clone_performance.rsThe clone cost of collections, and how to prevent unnecessary clones
01_06_drop_order.rsDrop order of local variables, struct fields, and Vec elements, and the early drop