Lesson 12 of 94
Move, Copy & Clone
The previous lesson showed that assigning a String moves it, while assigning an integer copies it. This lesson makes the rule precise and shows the third option, cloning, which you use whenever you really want two independent values.
What happens on let b = a; |
a still usable? |
Cost | |
|---|---|---|---|
Move (String, Vec, most types) |
Ownership transfers to b |
No | Copies a few bytes of header |
Copy (i32, f64, bool, char, ...) |
Bits are duplicated | Yes | Copies a few bytes |
Clone (let b = a.clone();) |
A full, independent duplicate is built | Yes | Allocates and copies all the data |
Rust makes every copy of heap data explicit and visible in the code.
1. Which types are Copy
A type is Copy when duplicating its bits is a complete, safe copy, so there is no heap data involved. These are all Copy:
- all integer types, all float types,
bool,char - tuples and arrays whose elements are all
Copy - shared references
&T, covered in References & Borrowing
fn main() {
let count = 10;
let ratio = 0.5;
let flag = true;
let letter = 'z';
let point = (3, 4);
let rgb = [255, 128, 0];
let (c2, r2, f2, l2, p2, rgb2) = (count, ratio, flag, letter, point, rgb);
// every original is still usable
println!("{count} {ratio} {flag} {letter} {:?} {:?}", point, rgb);
println!("{c2} {r2} {f2} {l2} {:?} {:?}", p2, rgb2);
}10 0.5 true z (3, 4) [255, 128, 0]
10 0.5 true z (3, 4) [255, 128, 0]2. A tuple is Copy only if every part is
fn main() {
let dimensions = (1920, 1080, 60.0); // (i32, i32, f64): all Copy
let saved = dimensions;
println!("{:?} and {:?}", dimensions, saved);
}(1920, 1080, 60.0) and (1920, 1080, 60.0)Add one String and the whole tuple moves instead:
fn main() {
let labeled = (1920, 1080, String::from("Full HD"));
let saved = labeled;
println!("{:?}", labeled);
}error[E0382]: borrow of moved value: `labeled`
--> src/main.rs:4:22
|
2 | let labeled = (1920, 1080, String::from("Full HD"));
| ------- move occurs because `labeled` has type `(i32, i32, String)`, which does not implement the `Copy` trait
3 | let saved = labeled;
| ------- value moved here
4 | println!("{:?}", labeled);
| ^^^^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let saved = labeled.clone();
| ++++++++3. An array is Copy only if its elements are
fn main() {
let numbers = [1, 2, 3];
let numbers_copy = numbers; // fine: [i32; 3] is Copy
let names = [String::from("a"), String::from("b")];
let names_moved = names; // moves: [String; 2] is not Copy
println!("{:?} {:?}", numbers, numbers_copy);
println!("{:?}", names);
}error[E0382]: borrow of moved value: `names`
--> src/main.rs:8:22
|
5 | let names = [String::from("a"), String::from("b")];
| ----- move occurs because `names` has type `[String; 2]`, which does not implement the `Copy` trait
6 | let names_moved = names; // moves: [String; 2] is not Copy
| ----- value moved here
7 | println!("{:?} {:?}", numbers, numbers_copy);
8 | println!("{:?}", names);
| ^^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
6 | let names_moved = names.clone(); // moves: [String; 2] is not Copy
| ++++++++4. clone() makes a deep, independent copy
.clone() allocates new heap memory and copies the data into it. The two values are completely independent afterwards.
fn main() {
let original = String::from("draft");
let mut edited = original.clone();
edited.push_str(" v2");
println!("original: {original}");
println!("edited: {edited}");
}original: draft
edited: draft v2 original ┌─────┐ heap
│ ptr ├──────► "draft"
└─────┘
edited ┌─────┐
│ ptr ├──────► "draft v2" (a separate allocation)
└─────┘5. Cloning a Vec
fn main() {
let prices = vec![10, 20, 30];
let mut discounted = prices.clone();
for p in &mut discounted {
*p -= 5;
}
println!("prices: {:?}", prices);
println!("discounted: {:?}", discounted);
}prices: [10, 20, 30]
discounted: [5, 15, 25]6. Cloning a Vec<String> clones every String
clone() is deep: cloning a vector of strings allocates a new vector and a new copy of every string in it.
fn main() {
let team = vec![String::from("Ana"), String::from("Ben")];
let mut backup = team.clone();
backup[0].push_str(" (lead)");
backup.push(String::from("Cy"));
println!("team: {:?}", team);
println!("backup: {:?}", backup);
}team: ["Ana", "Ben"]
backup: ["Ana (lead)", "Ben", "Cy"]7. Fixing "moved into a Vec" with clone
When you need the value in a vector and afterwards, clone it before pushing.
fn main() {
let mut audit_log = Vec::new();
let username = String::from("root");
audit_log.push(username.clone());
println!("logged in as {username}");
println!("audit log: {:?}", audit_log);
}logged in as root
audit log: ["root"]8. Fixing "moved in a previous iteration" with clone
This is the fix for the loop example from the previous lesson: each iteration pushes its own copy.
fn main() {
let report = String::from("quarterly numbers");
let mut outbox = Vec::new();
for _ in 0..3 {
outbox.push(report.clone());
}
println!("{} copies, original still here: {report}", outbox.len());
}3 copies, original still here: quarterly numbers9. Fixing "cannot move out of index" with clone
To get an owned copy of one element and leave the vector intact, clone that element.
fn main() {
let names = vec![String::from("alpha"), String::from("beta")];
let mut first = names[0].clone();
first.push_str("-copy");
println!("{first}");
println!("{:?}", names);
}alpha-copy
["alpha", "beta"]10. Copy elements come out of a Vec freely
Reading an element of a Copy type copies it, so there is no problem taking it "out" by index.
fn main() {
let latencies = vec![120, 85, 300];
let first = latencies[0];
let worst = latencies[2];
println!("first={first} worst={worst} all={:?}", latencies);
}first=120 worst=300 all=[120, 85, 300]11. to_string() and to_owned() create owned copies of text
A string literal is borrowed text (&str). to_string(), to_owned(), and String::from all make an owned String copy of it. They do the same thing; pick one style.
fn main() {
let literal = "config.toml";
let a = literal.to_string();
let b = literal.to_owned();
let c = String::from(literal);
println!("{a} {b} {c} {literal}");
}config.toml config.toml config.toml config.toml12. A clone's capacity fits its length
A clone allocates just enough memory for the current contents. Spare capacity is not copied.
fn main() {
let mut buffer = String::with_capacity(1024);
buffer.push_str("small");
let snapshot = buffer.clone();
println!("buffer: len={} capacity={}", buffer.len(), buffer.capacity());
println!("snapshot: len={} capacity={}", snapshot.len(), snapshot.capacity());
}buffer: len=5 capacity=1024
snapshot: len=5 capacity=5String::with_capacity(1024) reserves space up front.
13. Copy is implicit, Clone is explicit
Every Copy type is also Clone, so calling .clone() on an integer works, but it is pointless: the value would have been copied anyway. Clippy warns about it.
fn main() {
let a = 7;
let b = a.clone(); // works, but just write `let b = a;`
let c = a;
println!("{a} {b} {c}");
}7 7 7The deeper rule: a type can only be Copy if copying its bits is a complete copy. String cannot be Copy, because copying its bits would duplicate the pointer and lead to the double free described in Ownership.
14. Clones are independent in both directions
fn main() {
let mut original = vec![1, 2, 3];
let copy = original.clone();
original.push(4);
original[0] = 100;
println!("original: {:?}", original);
println!("copy: {:?}", copy);
}original: [100, 2, 3, 4]
copy: [1, 2, 3]15. Clone to keep the original order while sorting
fn main() {
let arrivals = vec![
(38, String::from("Chen")),
(52, String::from("Ada")),
(41, String::from("Bo")),
];
let mut by_score = arrivals.clone();
by_score.sort();
by_score.reverse();
println!("in arrival order:");
for (score, name) in &arrivals {
println!(" {name}: {score}");
}
println!("leaderboard:");
for (rank, (score, name)) in by_score.iter().enumerate() {
println!(" #{} {name}: {score}", rank + 1);
}
}in arrival order:
Chen: 38
Ada: 52
Bo: 41
leaderboard:
#1 Ada: 52
#2 Bo: 41
#3 Chen: 38Tuples sort by their first element, then their second, so sorting (score, name) pairs sorts by score.
16. When a clone is a smell
Cloning just to satisfy the compiler is the most common beginner habit. It works, but it copies data for no reason:
fn main() {
let title = String::from("Rust for Java Developers");
let length = title.clone().len(); // copies the whole string just to measure it
println!("{title} has {length} bytes");
}Rust for Java Developers has 24 bytestitle.len() only needs to look at the string, and looking never requires ownership. Rust's tool for looking without owning is the reference, and calling a method like title.len() already uses one behind the scenes:
fn main() {
let title = String::from("Rust for Java Developers");
let length = title.len(); // no clone needed
println!("{title} has {length} bytes");
}Rust for Java Developers has 24 bytesRule of thumb: clone when you need a second, independent value that you will change or keep separately. When you only need to read, borrow instead. References & Borrowing covers this in depth.
17. Moves are free, clones are not
A move of a Vec with a million elements copies 24 bytes. A clone copies a million elements. This example measures the difference in memory.
fn main() {
let big: Vec<u64> = vec![7; 1_000_000];
let moved = big; // 24 bytes copied
let cloned = moved.clone(); // 8 MB allocated and copied
let bytes = cloned.len() * std::mem::size_of::<u64>();
println!("each vector holds {} MB of data", bytes / 1_000_000);
println!("a move copied {} bytes", std::mem::size_of::<Vec<u64>>());
}each vector holds 8 MB of data
a move copied 24 bytesThis is why Rust makes clones explicit: you can find every expensive copy by searching for .clone().
18. Cloning Option and tuple values
clone() works on compound values too, cloning every part.
fn main() {
let pair = (String::from("key"), vec![1, 2]);
let pair_copy = pair.clone();
println!("{:?} {:?}", pair, pair_copy);
let maybe_name = Some(String::from("Ferris"));
let copy_of_name = maybe_name.clone();
println!("{:?} {:?}", maybe_name, copy_of_name);
}("key", [1, 2]) ("key", [1, 2])
Some("Ferris") Some("Ferris")19. A value moved out can be cloned first
When one piece of code needs to consume a value and you still need it afterwards, clone it right where it is consumed.
fn main() {
let words = vec![String::from("stop"), String::from("go")];
let mut total = 0;
for w in words.clone() { // the loop consumes the clone
total += w.len();
}
println!("{total} bytes in {:?}", words);
}6 bytes in ["stop", "go"]Here for w in &words would be better, because it avoids the clone entirely. The clone is shown so you can recognize the pattern in other code.
20. Realistic example: undo history with snapshots
An editor keeps a snapshot of the document before each edit. Each snapshot must be an independent copy, otherwise later edits would change the saved history. This is exactly what clone is for.
fn main() {
let mut document = vec![String::from("# Title")];
let mut history: Vec<Vec<String>> = Vec::new();
history.push(document.clone());
document.push(String::from("First paragraph."));
history.push(document.clone());
document.push(String::from("Second paragraph."));
history.push(document.clone());
document[0] = String::from("# Better Title");
println!("current: {:?}", document);
println!("undo once: {:?}", history[history.len() - 1]);
println!("undo all: {:?}", history[0]);
}current: ["# Better Title", "First paragraph.", "Second paragraph."]
undo once: ["# Title", "First paragraph.", "Second paragraph."]
undo all: ["# Title"]21. Realistic example: configuration templates
A base configuration is cloned for each environment and then customized. Every environment gets its own copy, and the base stays untouched.
fn main() {
let base: Vec<(String, String)> = vec![
(String::from("log_level"), String::from("info")),
(String::from("pool_size"), String::from("10")),
(String::from("region"), String::from("eu-west-1")),
];
let mut dev = base.clone();
dev[0].1 = String::from("debug");
let mut prod = base.clone();
prod[1].1 = String::from("50");
prod.push((String::from("alerts"), String::from("pagerduty")));
for (name, config) in [("base", &base), ("dev", &dev), ("prod", &prod)] {
print!("{name:5}:");
for (key, value) in config {
print!(" {key}={value}");
}
println!();
}
}base : log_level=info pool_size=10 region=eu-west-1
dev : log_level=debug pool_size=10 region=eu-west-1
prod : log_level=info pool_size=50 region=eu-west-1 alerts=pagerduty22. Realistic example: fan-out notifications
One alert is delivered to several channels, and each channel's queue owns its own copy of the message.
fn main() {
let alert = format!("[CRITICAL] {} is down", "checkout-service");
let mut email_queue = Vec::new();
let mut sms_queue = Vec::new();
let mut chat_queue = Vec::new();
email_queue.push(alert.clone());
sms_queue.push(alert.clone());
chat_queue.push(alert); // the last one can take the original: no clone needed
println!("email: {:?}", email_queue);
println!("sms: {:?}", sms_queue);
println!("chat: {:?}", chat_queue);
}email: ["[CRITICAL] checkout-service is down"]
sms: ["[CRITICAL] checkout-service is down"]
chat: ["[CRITICAL] checkout-service is down"]Notice the last push moves the original instead of cloning it, which saves one allocation. Clippy points out a clone right before the last use of a value (redundant_clone). When many owners need the same large value without copying it, Rust has Rc and Arc, covered in Rc & Weak.
23. Realistic example: a shopping cart with a saved-for-later list
Moving and cloning together: moving an item between lists transfers it, and "buy again" clones it so it stays in the order history.
fn main() {
let mut cart = vec![String::from("headphones"), String::from("cable"), String::from("case")];
let mut saved_for_later = Vec::new();
let mut order_history = vec![String::from("charger")];
// move "cable" from the cart to saved-for-later: no copy
let item = cart.remove(1);
saved_for_later.push(item);
// "buy again": copy from history into the cart, history keeps its entry
cart.push(order_history[0].clone());
// checkout: move everything from the cart into history
for item in cart {
order_history.push(item);
}
println!("saved for later: {:?}", saved_for_later);
println!("order history: {:?}", order_history);
}saved for later: ["cable"]
order history: ["charger", "headphones", "case", "charger"]After the checkout loop, cart has been consumed. Trying to print it would be an E0382 error, because every item now belongs to order_history.
24. Realistic example: building test fixtures
Tests often start from the same data and change one thing. Cloning a template keeps each case isolated.
fn is_valid_user(user: &(String, String, u32)) -> bool {
let (name, email, age) = user;
!name.is_empty() && email.contains('@') && *age >= 13
}
fn main() {
let template = (String::from("sam"), String::from("sam@example.com"), 30);
let mut no_name = template.clone();
no_name.0 = String::new();
let mut bad_email = template.clone();
bad_email.1 = String::from("sam.example.com");
let mut too_young = template.clone();
too_young.2 = 9;
println!("template: {}", is_valid_user(&template));
println!("no name: {}", is_valid_user(&no_name));
println!("bad email: {}", is_valid_user(&bad_email));
println!("too young: {}", is_valid_user(&too_young));
}template: true
no name: false
bad email: false
too young: falseThe function takes &(String, String, u32), a reference to the tuple, so it can check a user without taking ownership of it. The next two lessons explain these references and why *age is needed.
Summary
Copytypes (numbers,bool,char, and tuples and arrays made only of them) are duplicated on assignment. Both variables stay valid.- Every other type moves on assignment. Only a small header is copied, and the old variable becomes invalid.
.clone()creates a deep, independent copy, allocating new heap memory. It is always explicit.- Clone when you need a second value that lives or changes independently. When you only need to read, borrow instead (next lessons).
- Avoid a clone right before the last use of a value: move it instead.
Next: Ownership & Functions.
