The move that breaks your code
You write a function that takes a String. You pass your variable in. The code compiles. You try to print the variable again after the call. The compiler screams E0382. You stare at the screen. In Python, this works fine. In JavaScript, objects are passed by reference automatically. Rust feels like it's stealing your data. It isn't stealing. It's moving. And that move is the feature, not the bug.
The error E0382 (use of moved value) appears because Rust transferred ownership of the data to a new variable or function. The original variable is now invalid. The compiler prevents you from using it because the data no longer lives where you think it does.
Why Rust moves everything
Rust treats owned data like a unique physical object. When you assign let b = a, you aren't making a photocopy. You are handing the object to b. a no longer holds the object. a is now empty. If you try to use a, you're trying to hand over something you don't have.
This rule exists to prevent double-free bugs. If Rust allowed a and b to both own the same heap allocation, both variables would try to free the memory when they go out of scope. The second free would corrupt memory. By enforcing a single owner, Rust guarantees memory is freed exactly once. The move is the mechanism that tracks that single owner.
The move is the safety net. Don't patch it with clones.
The Copy trait exception
Not everything moves. Try this:
fn main() {
let x = 5;
let y = x; // Copy, not move.
println!("{x}"); // Works. x is still valid.
}
This compiles without error. i32 implements the Copy trait. Types that are Copy are bitwise duplicated instead of moved. The compiler inserts a copy instruction behind the scenes. x remains valid because y holds an independent duplicate.
Copy is reserved for data that lives entirely on the stack and has no cleanup logic. Integers, booleans, characters, and references are Copy. String is not Copy because it owns heap memory. Cloning a String requires allocating new heap memory and copying the bytes, which is expensive. Rust forces you to be explicit about that cost.
Convention aside: if you define a struct and want it to be Copy, you must derive the trait. Every field in the struct must also be Copy.
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = p1; // Copy.
println!("{p1.x}"); // Works.
}
If you forget #[derive(Copy, Clone)], the struct moves like a String. The compiler will reject use of the original variable.
Fix 1: Borrow with a reference
Most of the time, you don't want to move data. You want to look at it or modify it without taking ownership. Use a reference. A reference borrows the data. The owner remains responsible for cleanup. The borrower can use the data for a limited time.
fn main() {
let s1 = String::from("hello");
// Pass a reference. s1 keeps ownership.
takes_reference(&s1);
println!("{s1}"); // Works. s1 is still valid.
}
/// Prints the string without taking ownership.
fn takes_reference(s: &String) {
// s borrows the data. No ownership transfer.
println!("{s}");
}
The ampersand & creates a reference. s1 is not moved. takes_reference receives a borrowed pointer. When the function returns, the borrow ends. s1 remains usable.
Convention aside: when accepting a string reference, prefer &str over &String. &str works for both String and string literals. It's more flexible. The compiler coerces &String to &str automatically. Writing fn takes_reference(s: &str) allows callers to pass &s1 or "hello" without extra work.
Borrow by default. Clone only when you measure the cost.
Fix 2: Clone when you need a copy
If you genuinely need a second independent copy of the data, call clone(). This allocates new memory and duplicates the content. The original value remains valid. The clone is a separate owner.
fn main() {
let s1 = String::from("hello");
// Create an independent copy.
let s2 = s1.clone();
println!("{s1}"); // Works. s1 is still valid.
println!("{s2}"); // Works. s2 owns its own data.
}
Use clone() sparingly. It has a runtime cost proportional to the size of the data. In performance-critical loops, cloning large strings or vectors can dominate execution time. Profile before cloning. If you find yourself cloning everywhere, your API design might be fighting ownership. Consider restructuring to pass references or return values instead.
Fix 3: Return ownership
Sometimes a function needs to transform data. It takes ownership, does work, and returns the result. The caller receives ownership back. This pattern avoids cloning and keeps memory efficient.
fn main() {
let s1 = String::from("hello");
// s1 moves into the function.
let s2 = transform(s1);
// s1 is invalid here. s2 owns the result.
println!("{s2}");
}
/// Appends " world" and returns the new String.
fn transform(mut input: String) -> String {
input.push_str(" world");
input
}
s1 moves into transform. Inside the function, input owns the data. The function returns input. Ownership moves back to s2. s1 is never used after the move. This is the idiomatic way to mutate owned data.
Design your API around ownership. The compiler will enforce it.
Realistic scenario: Methods and self
Structs often need methods that either consume the struct or borrow it. The choice depends on whether the method changes the struct's identity or just inspects it.
struct Config {
name: String,
verbose: bool,
}
impl Config {
/// Consumes the config and prints a summary.
/// After this call, the Config is gone.
fn consume(self) {
println!("Consuming config: {}", self.name);
// self is dropped here. Memory is freed.
}
/// Borrows the config to inspect it.
/// The Config remains usable after this call.
fn inspect(&self) {
println!("Inspecting config: {}", self.name);
}
/// Mutably borrows to update a field.
fn enable_verbose(&mut self) {
self.verbose = true;
}
}
fn main() {
let mut config = Config {
name: String::from("app"),
verbose: false,
};
config.inspect(); // Borrows. config is still valid.
config.enable_verbose(); // Mutably borrows. config is still valid.
config.consume(); // Moves. config is invalid after this.
// println!("{:?}", config); // ERROR: E0382. config was moved.
}
self takes ownership. &self borrows immutably. &mut self borrows mutably. Pick the signature that matches the operation. If the method doesn't need to own the struct, use a borrow. If you use self unnecessarily, callers will hit E0382 when they try to use the struct later.
Pitfalls: Partial moves and closures
Ownership errors get subtle when you move parts of a struct or capture variables in closures.
Partial moves happen when you move a field out of a struct. The struct becomes partially moved. You can't use the struct as a whole anymore.
fn main() {
let pair = (String::from("hello"), String::from("world"));
let first = pair.0; // Move first field.
// pair is partially moved.
// println!("{:?}", pair); // ERROR: E0382. pair.0 was moved.
println!("{first}"); // Works.
}
If you try to move a field out of a borrowed struct, you hit E0507 (cannot move out of borrowed content). You can't take a piece of something you don't own.
fn main() {
let pair = (String::from("hello"), String::from("world"));
// pair is borrowed.
let first = &pair.0; // Borrow the field. OK.
// let first = pair.0; // ERROR: E0507. Cannot move out of borrowed content.
}
Closures capture variables by move by default if they use owned data. The closure takes ownership of the captured variables.
fn main() {
let s = String::from("data");
// Closure captures s by move.
let closure = || println!("{s}");
// println!("{s}"); // ERROR: E0382. s moved into closure.
closure(); // Works.
}
If you need to use s after the closure, pass a reference into the closure or use move carefully. Closures that capture references borrow the data. The borrow lasts as long as the closure lives.
Decision matrix
Use a reference (&T) when you need to read or modify data without taking ownership. This is the default choice for function arguments and method receivers.
Use clone() when you genuinely need a second independent copy of the data. Accept the performance cost.
Use Rc<T> when multiple owners need to share read-only data across a graph or tree structure. Reference counting handles cleanup automatically.
Return the value when the function transforms data and the caller should own the result. This avoids cloning and keeps ownership clear.
Reach for Cow<'_, str> when you want to accept borrowed data but might need to mutate it, avoiding clones when possible. Cow stands for "clone on write". It holds either a borrowed reference or an owned value.
Use Copy types for small, stack-only data like integers and booleans. Derive Copy for structs that contain only Copy fields.
Reach for unsafe only when you are implementing a safe abstraction yourself or calling FFI. Never use unsafe to bypass ownership rules in application code.
Ownership is a design decision. Make it explicit.