Why does Rust move values instead of copying them?
Short answer: assigning a String does not copy its data, it transfers ownership. Rust refuses to
leave two variables pointing at the same heap buffer, because freeing it twice would be a memory bug.
The variable you assigned from is not pointing at anything any more — so the compiler stops you from
using it.
The example that trips everyone up
Section titled “The example that trips everyone up”fn main() { let s1 = String::from("hello"); let s2 = s1;
println!("{}", s2); // fine println!("{}", s1); // error[E0382]: borrow of moved value: `s1`}At first glance this looks like a bug. Both lines print a string — why is one of them illegal?
What actually happened
Section titled “What actually happened”A String is three words on the stack: a pointer to a heap buffer, a length, and a capacity. The
text itself lives on the heap.
flowchart LR A["s1 — owns the heap buffer"] -->|"let s2 = s1"| B["s2 — now owns the heap buffer"] A -.-> C["s1 — moved out, unusable"]
If Rust copied the three stack words the way a language with a garbage collector might, s1 and s2
would both believe they own the same heap buffer. When both go out of scope, the buffer is freed
twice. That is a double free — and in C it is exactly the kind of bug Rust exists to make impossible.
So the compiler makes a choice: it moves the value. Ownership transfers to s2, and s1 becomes
invalid. Using it afterwards is the E0382 error above.
Types that copy instead of moving
Section titled “Types that copy instead of moving”Not everything moves. Simple values that live entirely on the stack implement the Copy trait, and
assignment copies them:
fn main() { let a = 5; let b = a;
println!("{a} {b}"); // both are valid}i32, f64, bool, char, and references are all Copy. Tuples and arrays of Copy types are
Copy too, so (1, 2) is copied while (String, i32) is moved.
The rule of thumb: if the type manages something on the heap, or implements Drop to clean up after
itself, it does not implement Copy.
How to keep using a value after passing it on
Section titled “How to keep using a value after passing it on”Three options, in order of preference:
Borrow it
Section titled “Borrow it”If the function only needs to read the value, pass a reference and nothing moves:
fn print_length(s: &String) { println!("{} characters", s.len());}
fn main() { let s1 = String::from("hello"); print_length(&s1); // borrowed, not moved println!("{s1}"); // still valid}Clone it
Section titled “Clone it”If you genuinely need two independent values, say so explicitly:
fn main() { let s1 = String::from("hello"); let s2 = s1.clone(); // deep copy of the heap data
println!("{s1} {s2}");}clone() is honest but not free — it allocates and copies. Reaching for it whenever the borrow
checker complains is the most common way to write slow Rust. Try borrowing first.
Give it back
Section titled “Give it back”A function that takes ownership can return it:
fn shout(mut s: String) -> String { s.make_ascii_uppercase(); s}
fn main() { let s1 = String::from("hello"); let s1 = shout(s1); // moved in, moved back out println!("{s1}"); // HELLO}