Step 4 of 6
Copy types and clone
Why did let b = a; leave a usable when a was an integer? Because i32 is a Copy type. Its value lives entirely on the stack and copying it is as cheap as moving it, so Rust just copies it and both variables stay valid. There's no heap block that could be freed twice. (A trait is a capability a type can have; you'll write your own in the traits module. Copy is the capability "can be duplicated by copying its bytes".)
These types are Copy:
- all integers (
i32,u8,usize, ...) and floats (f64,f32) boolandchar- tuples and arrays made only of
Copytypes:(i32, bool),[f64; 3] - string literals (
&str): copying one copies a pointer to text stored in the program, never the text itself
These are not Copy: String, Vec<T>, and anything that contains one, like (String, i32) or [String; 2]. The rule of thumb: if a type owns heap memory, it isn't Copy, because copying its handle would create two owners.
fn main() {
let score = 1200; // i32 is Copy
let pos = (3, 4); // a tuple of Copy types is Copy
let best = score;
let start = pos;
println!("{} {}", score, best);
println!("{:?} {:?}", pos, start);
}
1200 1200
(3, 4) (3, 4)
One String inside a tuple is enough to make the whole tuple move:
fn main() {
let player = (String::from("Ferris"), 100);
let backup = player;
println!("{:?} {:?}", player, backup);
}
error[E0382]: borrow of moved value: `player`
|
2 | let player = (String::from("Ferris"), 100);
| ------ move occurs because `player` has type `(String, i32)`, which does not implement the `Copy` trait
clone: an explicit deep copy
When you really need two independent Strings or Vecs, ask for one with .clone(). It allocates a new heap block and copies every byte into it:
let a = String::from("hi");
let b = a.clone();
a b
┌────────────┐ ┌────────────┐
│ ptr ───────┼──┐ │ ptr ───────┼──┐
│ len 2 │ │ │ len 2 │ │
│ cap 2 │ │ │ cap 2 │ │
└────────────┘ ▼ └────────────┘ ▼
┌───┬───┐ ┌───┬───┐
HEAP │ h │ i │ │ h │ i │
└───┴───┘ └───┴───┘
Two owners, two heap blocks: each is freed once, by its own owner. Changing one doesn't affect the other:
fn main() {
let defaults = vec![String::from("sound: on"), String::from("music: on")];
let mut custom = defaults.clone();
custom.push(String::from("subtitles: on"));
println!("defaults: {:?}", defaults);
println!("custom: {:?}", custom);
}
defaults: ["sound: on", "music: on"]
custom: ["sound: on", "music: on", "subtitles: on"]
What clone costs
Cloning a String means one allocation plus copying all of its bytes. Cloning a Vec<String> clones the vector and every string in it: here, three allocations. For a few short strings, nobody will notice. Inside a loop that runs a million times, or on the contents of a 50 MB file, you will. A move, by contrast, always copies just the 24-byte handle.
Workplace note: .clone() is the quickest way to make an ownership error go away, which is why reviewers ask "do we really need a copy here?" when they see one. Often the better answer is borrowing (next module). Clone when you truly need two independent values, for example when you'll change one and must keep the other as it was. Cloning a small string once is fine; don't feel bad about it. And cargo clippy warns about .clone() on a Copy type (the clone_on_copy lint): just write x.
Common mistakes
- Cloning to silence every error. It compiles, but it hides a design question and costs memory and time.
- Expecting a tuple or array with a
Stringinside to beCopy. It's onlyCopyif every part is. - Calling
.clone()on ani32orbool. It works, but it's noise:Copytypes copy themselves.
Your task
template must stay unchanged while letter gets extra text. Right now template moves into letter, so it can't be printed afterwards. Add one .clone() in the right place so the program prints:
Dear customer,
Dear customer, your order has shipped.
Challenge 2
The program saves the old high score, champion and lineup, then changes the current ones. It doesn't compile: two of the three "saves" are moves. Add .clone() only where it's needed: high_score is an i32, which is Copy, so it must not be cloned. The program should print:
was: 9500 by Ferris (2 players)
now: 9800 by Ferris II (3 players)
Challenge 3
The program reads player names, one per line. Each name should be moved into players, and a copy should be kept in longest whenever the name is longer than the longest so far (the first one wins a tie). Fill in the loop body. For the input:
Ana
Bartholomew
Cy
it prints:
3 players
longest name: Bartholomew
(Vec::new() creates an empty vector, and the annotation Vec<String> says what it will hold. std::io::stdin().lines() gives you each input line as an owned String, wrapped in a Result, which is why each line needs .expect(...).)
Previous: Moves: one owner at a time Next: Ownership and functions