When should you take a reference instead of cloning?
Short answer: Look at the signature of the function you are calling. If it takes &str (or &T),
pass a reference — nothing is copied. If it takes String (or T), it wants to own the value, so
you either hand it over or clone it first. The clone is not forced on you by the borrow checker; it
is forced by the signature. Which is why the usual fix is to change the signature, not to clone.
The clone that satisfies the signature
Section titled “The clone that satisfies the signature”Start from a fresh project — cargo new ref-not-clone, then replace src/main.rs with the whole of
each file below.
fn shout(name: String) { println!("{}!", name.to_uppercase());}
fn main() { let name = String::from("Ada"); shout(name.clone()); println!("hello {name}");}ADA!hello AdaThis compiles and does the right thing. And it looks like the borrow checker made you clone. It did
not. shout takes String by value: the argument is moved in, shout owns it for the duration
of the call, and when shout returns the string is gone. The only way to still have a name
afterwards is to pass a copy — so the caller clones.
name.clone() allocates a brand-new String on the heap and copies every byte into it, just so the
original survives a function that only ever reads it. For "Ada" that is invisible. For a
ten-megabyte log line inside a loop, that clone is the program’s cost.
What happens without the clone
Section titled “What happens without the clone”Drop the .clone() and the compiler tells you exactly what the signature demanded:
fn shout(name: String) { println!("{}!", name.to_uppercase());}
fn main() { let name = String::from("Ada"); shout(name); println!("hello {name}");}error[E0382]: borrow of moved value: `name` --> src/main.rs:8:22 |6 | let name = String::from("Ada"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait7 | shout(name); | ---- value moved here8 | println!("hello {name}"); | ^^^^ value borrowed here after move |note: consider changing this parameter type in function `shout` to borrow instead if owning the value isn't necessary --> src/main.rs:1:16 |1 | fn shout(name: String) { | ----- ^^^^^^ this parameter takes ownership of the value | | | in this functionhelp: consider cloning the value if the performance cost is acceptable |7 | shout(name.clone()); | ++++++++
For more information about this error, try `rustc --explain E0382`.shout(name) moves the string into the call. The variable name is empty afterwards — the value it
owned is gone — so the later println! is a use of a moved value, E0382. Nothing here is about
borrowing at all: the signature said “I take ownership”, and the caller cannot keep what it gave
away.
Change the signature, not the caller
Section titled “Change the signature, not the caller”A function that only reads the string should not ask for String. Let the signature say what the
body actually does:
fn shout(name: &str) { println!("{}!", name.to_uppercase());}
fn main() { let name = String::from("Ada"); shout(&name); println!("hello {name}");}ADA!hello AdaSame output, no clone, no error. &name creates a reference to the string’s data, and the borrow
ends when shout returns — the caller keeps the String, untouched. Two details make this the
natural choice:
&stris a borrowed view of string data, so it accepts aString’s contents without owning them.- Passing
&name— an&String— works anyway, because&Stringautomatically converts to&strat the call site (deref coercion). Callers never have to convert by hand.
A reference is a pointer and a length — two machine words, no matter how long the string is. A clone is a heap allocation plus a full copy of the bytes. That is the whole trade, and the signature is where you choose.
When the clone is real
Section titled “When the clone is real”Some functions do need to own the value: they store it somewhere that outlives the call — into a
struct field, a Vec, a thread. Ownership is then the honest signature, and one copy has to happen
somewhere. Put that copy in exactly one place: inside the function that keeps the data.
struct Player { name: String,}
impl Player { fn new(name: &str) -> Player { Player { name: name.to_string(), } }}
fn main() { let name = String::from("Ada"); let player = Player::new(&name); println!("{} vs {}", name, player.name);}Ada vs AdaThe constructor takes &str and does the single .to_string() copy that has to exist, because the
struct owns its name after the call. If new took String instead, every caller holding only a
&str — or a String it still needs — would have to clone at the call site, and the same one copy
would get made in a different place each time.
The rule of thumb falls out of this: functions borrow, owners own. A function that only looks at data takes a reference. A function that keeps data takes ownership — or takes a reference and does the one unavoidable copy inside, right next to the field it lands in. What you should never do is clone at a call site to stop a move error: that error is the signature telling you it wants ownership, and the answer is either give it or change the signature.
Related
Section titled “Related”- How to borrow a value — what
&does and why it stops a move - What is the difference between & and &mut? — reading through a borrow vs writing through one
- How long does a borrow last? — why the borrow of
nameends whenshoutreturns - Borrowing — the rest of this chapter
- Next in this chapter: ending a borrow early