Ownership · about 35 min

Copy, Clone and the Cost of Duplicating

Why this matters

If let s2 = s1; moves a String, why does let y = x; with an integer leave x perfectly usable? And when you really do need two independent copies of a String, how do you get them? The answers are two traits, Copy and Clone. Understanding the difference tells you which assignments are free, which are expensive, and why Rust insists on making the expensive ones visible.

Integers do not move

fn main() {
    let x = 5;
    let y = x;
    println!("x = {x}, y = {y}");

    let p = (1.5, 'z', true);
    let q = p;
    println!("{:?} {:?}", p, q);
}
x = 5, y = 5
(1.5, 'z', true) (1.5, 'z', true)

No E0382. An i32 is four bytes on the stack with no heap memory attached. Copying those four bytes produces a complete, independent value; there is nothing that could be freed twice. So for such types, Rust copies on assignment and leaves the original valid. These types implement the Copy trait. (A trait is a named capability a type can have; module 10 covers them in depth. For now, "implements Copy" means "duplicates itself on assignment".)

let x = 5;  let y = x;               let s1 = String::from("hi");  let s2 = s1;

 x | 5 |     y | 5 |                   s1 | (moved) |     s2 | ptr -----> [h|i]
                                                              | len 2 |
 both valid, fully independent                                | cap 2 |
                                       one owner of the heap buffer

Both cases do the same thing at the machine level: copy the bytes of the stack value. The only difference is whether the compiler lets you use the source afterwards, and that depends on whether the bytes alone are the whole value.

Which types are Copy

Not Copy: anything that owns a resource and needs cleanup: String, Vec, Box, files, network connections. The rule is strict: a type that needs to run cleanup code when dropped can never be Copy. Otherwise a bitwise copy would produce two owners of one resource.

Clone: explicit deep copies

When you need a second, independent String, ask for it with .clone():

fn main() {
    let s1 = String::from("hello");
    let mut s2 = s1.clone();
    s2.push_str(", world");
    println!("s1 = {s1}");
    println!("s2 = {s2}");
    println!("s1 buffer {:p}, s2 buffer {:p}", s1.as_ptr(), s2.as_ptr());
}
s1 = hello
s2 = hello, world
s1 buffer 0x556232352b10, s2 buffer 0x556232352b30

clone allocates a new heap buffer, copies the bytes, and returns a new String that owns it:

   s1 | ptr -----------> [h|e|l|l|o]
      | len 5
      | cap 5
                                                 two buffers at two addresses,
   s2 | ptr -----------> [h|e|l|l|o|,| |w|o|r|l|d]    two owners, each freed once
      | len 12
      | cap 12 (or more)

The two are completely independent: modifying s2 does not affect s1, and each frees its own buffer. Most standard types implement the Clone trait. Copy types are Clone too (cloning an i32 is just a copy).

The difference in one sentence: Copy is implicit and always cheap (a few bytes); Clone is explicit and may be expensive (proportional to the data). Rust never runs potentially expensive code behind a plain =. When you see .clone() in a program, you see a cost.

When to clone

Cloning is a legitimate tool. Use it when you genuinely need two independent values: keep the original while handing a modified copy elsewhere, or store the same name in two different collections. It is also fine while learning: a working program with a few clones beats a non-compiling one.

But treat each clone as a question. Often the code does not need a second copy at all:

A clone of a 10-character string costs nanoseconds. A clone of a 100 MB buffer inside a loop is a performance bug. Rust's explicitness makes both easy to spot in review.

Copy semantics with functions

Passing a value to a function follows the same rules as assignment: Copy types are copied, others are moved.

fn bump(mut n: i32) -> i32 {
    n += 1;
    n
}

fn main() {
    let a = 41;
    let b = bump(a);
    println!("a = {a}, b = {b}");
}
a = 41, b = 42

bump changed its own copy; a is untouched. Lesson 5 shows what happens when a String is passed.

Common mistakes

Expecting clone on a big value to be cheap. It copies everything.

Cloning to silence the compiler without thinking. Check whether a borrow would do.

Assuming a tuple is Copy because it is small:

fn main() {
    let pair = (1, String::from("one"));
    let other = pair;
    println!("{:?}", pair);
}
error[E0382]: borrow of moved value: `pair`
 --> main.rs:4:22
  |
2 |     let pair = (1, String::from("one"));
  |         ---- move occurs because `pair` has type `(i32, String)`, which does not implement the `Copy` trait
3 |     let other = pair;
  |                 ---- value moved here
4 |     println!("{:?}", pair);
  |                      ^^^^ value borrowed here after move
  |
help: consider cloning the value if the performance cost is acceptable
  |
3 |     let other = pair.clone();
  |                     ++++++++

One non-Copy element makes the whole tuple non-Copy: the label says the type (i32, String) does not implement Copy. (rustc also warns that other is unused; that warning is omitted here.)

Expecting a modification through a copy to affect the original. With Copy types, let mut y = x; y += 1; never changes x. That is a feature, but sometimes surprising when you wanted shared state; that is what references are for.

Recap

Going further: print the addresses ({:p} with as_ptr()) of a String before and after moving it into another variable. They are the same: a move never touches the heap data. Then do the same with clone.

Exercises

Exercise 1 · quiz

Copy or move?

For which of these types does let b = a; leave a usable afterwards? Select all that apply.

Answer
  • i64

  • (i32, char, bool)

  • [f64; 3]

  • &str

Types whose values are entirely contained in their fixed-size bytes, with no heap resource to free, are Copy: all integers and floats, bool, char, shared references like &str, and tuples and arrays made only of Copy types. Assignment duplicates the bits and both copies are valid. A String, or any tuple containing one, owns heap memory and therefore moves.

Exercise 2 · write code

When you really need two

The program needs two independent Strings: one is uppercased and the original must still be printed afterwards. It fails with E0382. Fix it with clone() so that it prints:

original: quiet please
loud: QUIET PLEASE

Read the original from stdin (one line, trimmed). The starter already does that.

File: main.rs

Starting point

use std::io;

fn shout(text: String) -> String {
    text.to_uppercase()
}

fn main() {
    let mut line = String::new();
    io::stdin().read_line(&mut line).unwrap();
    let original = line.trim().to_string();
    let loud = shout(original);
    println!("original: {original}");
    println!("loud: {loud}");
}

Example

input

quiet please

expected output

original: quiet please
loud: QUIET PLEASE

Example

input

Rust 2021

expected output

original: Rust 2021
loud: RUST 2021
Hint 1

shout(original.clone()) gives the function its own copy and keeps original.

Reference solution
use std::io;

fn shout(text: String) -> String {
    text.to_uppercase()
}

fn main() {
    let mut line = String::new();
    io::stdin().read_line(&mut line).unwrap();
    let original = line.trim().to_string();
    let loud = shout(original.clone());
    println!("original: {original}");
    println!("loud: {loud}");
}

Exercise 3 · write code

Arrays of numbers copy

Arrays of Copy types are themselves Copy. Show it: write fn double_all(mut a: [i32; 4]) -> [i32; 4] that doubles every element of its (local copy of the) array and returns it. In main, call it on let base = [1, 2, 3, 4]; and print both arrays to show that base is unchanged:

base: [1, 2, 3, 4]
doubled: [2, 4, 6, 8]

File: main.rs

Starting point

fn main() {
    let base = [1, 2, 3, 4];
}

Example

expected output

base: [1, 2, 3, 4]
doubled: [2, 4, 6, 8]
Hint 1

Loop over indexes: for i in 0..4 { a[i] *= 2; } (or for x in a.iter_mut() { *x *= 2; }).

Hint 2

Return a as the final expression. Passing base copies it, so base can still be printed.

Reference solution
fn double_all(mut a: [i32; 4]) -> [i32; 4] {
    for x in a.iter_mut() {
        *x *= 2;
    }
    a
}

fn main() {
    let base = [1, 2, 3, 4];
    let doubled = double_all(base);
    println!("base: {:?}", base);
    println!("doubled: {:?}", doubled);
}

Exercise 4 · quiz

What does clone cost?

let a = String::from(...) holds a 1 MB string. What does let b = a.clone(); do?

Answer
  • Allocates a new 1 MB heap buffer and copies all the bytes into it; a and b each own their own buffer

clone on a String is a deep copy: a fresh allocation of the same size plus a byte-by-byte copy. Afterwards the two are completely independent; changing one never affects the other, and each frees its own buffer. That independence is useful, and it costs time and memory proportional to the data. That is why Rust makes you write .clone() visibly instead of doing it behind =.