The variable vanished
You write a function to process a user profile. You pass the profile object to the function, then try to print the user's name right after the call. The compiler rejects the code with E0382: use of moved value. You stare at the screen. The variable is still in scope. The function finished. Why does Rust think the data disappeared?
In Python or JavaScript, you can pass an object to a function and still use it afterwards. Those languages use garbage collection or reference counting behind the scenes. Rust does not. Rust treats ownership as a strict transfer. When you pass a value, you hand over the responsibility for that data. The original variable becomes invalid. This rule prevents memory errors like double-free or use-after-free, but it requires a mental shift.
Rust doesn't hide memory management. It forces you to make the ownership flow explicit.
Ownership is a physical transfer
Rust treats data like a physical object you hand over. If you hand a book to a friend, you no longer hold the book. Your friend has it. If you try to read the book after handing it over, you'll find your hands empty. Rust applies this rule strictly to heap-allocated data like String, Vec, and custom structs. When you pass a value to a function or assign it to a new variable, ownership transfers. The original variable becomes invalid. This prevents two parts of your program from trying to free the same memory, which would crash the system.
The rule exists because Rust manages memory deterministically. When a variable goes out of scope, Rust calls its destructor to free the memory. If two variables owned the same heap data, both would try to free it when their scopes ended. The second free would corrupt memory. Rust eliminates this class of bugs by ensuring exactly one owner exists at any time.
Treat ownership as a key. If you give the key away, you can't open the door.
Minimal example: the move
The simplest way to trigger E0382 is assigning a value to a new variable. The assignment moves the data.
fn main() {
// s1 owns a String on the heap.
let s1 = String::from("hello");
// Ownership moves from s1 to s2.
// s1 is now invalid and cannot be used.
let s2 = s1;
// Compiler error E0382: use of moved value.
// s1 no longer owns the data.
println!("{}", s1);
}
The compiler tracks ownership through your code. When it sees let s2 = s1, it marks s1 as moved. Any subsequent use of s1 triggers E0382. The compiler doesn't guess. It knows exactly where the ownership transferred. This check happens at compile time, so there is zero runtime cost for ownership tracking. The safety is baked in before the program runs.
Trust the borrow checker. It usually has a point.
Realistic scenario: function boundaries
E0382 appears most often at function boundaries. Functions that take ownership consume the value. The caller loses access.
/// Consumes the string and prints it.
fn log_message(msg: String) {
println!("[LOG] {}", msg);
}
fn main() {
let message = String::from("System started");
// Ownership moves into log_message.
log_message(message);
// E0382: cannot use `message` after it has been moved.
println!("Message sent: {}", message);
}
The function signature fn log_message(msg: String) declares that the function takes ownership of the string. When you call log_message(message), the string moves into the function. Inside the function, msg owns the data. When the function returns, msg goes out of scope and the string is dropped. The variable message in main is already invalid after the call.
Community convention favors references for read-only access. Functions that only inspect data should take &T. Taking ownership (T) signals that the function consumes the value. If you see a function taking String instead of &str, it usually means the function stores the string or transfers it elsewhere.
Pass a reference by default. Clone only when you need independence.
Structs move too
Custom structs move when they contain owned data. Even if the struct is small, if it owns a String or Vec, the whole struct moves.
struct User {
name: String,
age: u32,
}
fn main() {
// user owns the struct and its fields.
let user = User {
name: String::from("Alice"),
age: 25,
};
// Ownership of the entire struct moves to user2.
let user2 = user;
// E0382: use of moved value.
// user is invalid.
println!("{}", user.name);
}
The move applies to the entire struct. You cannot access any field after the struct moves. This prevents partial ownership, which would complicate memory management. Rust keeps the rule simple: the owner controls the whole value.
If you need to share parts of a struct, pass references to those parts. Don't move the struct unless the receiver needs full ownership.
Why Rust moves by default
In garbage-collected languages, assignment creates a new reference. Both variables point to the same object. The runtime tracks how many references exist. When the count drops to zero, the object is freed. This approach is convenient but has costs. Reference counting adds overhead to every assignment. Garbage collection introduces pauses and unpredictable latency.
Rust avoids these costs by moving by default. Assignment transfers ownership. No counter needs to increment. No runtime needs to track references. The compiler enforces the rules statically. You pay the cost at compile time, not at runtime. This gives Rust the performance of C with stronger safety guarantees.
The trade-off is that you must be explicit when you want to share data. You borrow with & or clone with .clone(). The compiler forces you to choose. This choice makes the data flow visible in the code.
Pay the cost at compile time, not at runtime.
Fixing the error
Most E0382 errors fix by changing how you pass data. The solution depends on what the receiver needs.
If the function only reads the data, pass a reference. The caller keeps ownership.
/// Reads the string without taking ownership.
fn log_message(msg: &str) {
println!("[LOG] {}", msg);
}
fn main() {
let message = String::from("System started");
// Borrow the string. Ownership stays with message.
log_message(&message);
// message is still valid.
println!("Message sent: {}", message);
}
If the function needs to modify the data in place, pass a mutable reference. The caller retains ownership but allows mutation.
/// Modifies the string in place.
fn append_timestamp(msg: &mut String) {
msg.push_str(" at 12:00");
}
fn main() {
let mut message = String::from("System started");
// Borrow mutably. Ownership stays with message.
append_timestamp(&mut message);
// message is modified and still valid.
println!("{}", message);
}
If the function needs its own independent copy and the caller must also keep the original, clone the value. Cloning copies the data to a new allocation.
/// Takes ownership of a string.
fn store_message(msg: String) {
println!("Stored: {}", msg);
}
fn main() {
let message = String::from("System started");
// Clone creates a deep copy.
// Both message and the clone own separate data.
store_message(message.clone());
// message is still valid.
println!("Original: {}", message);
}
Cloning is expensive for heap data. It allocates memory and copies bytes. Use cloning sparingly. If you find yourself cloning everywhere, you might be fighting the borrow checker instead of working with it. Restructure the code to use references where possible.
clone() on Rc or Arc is cheap. clone() on String is expensive. Know the difference.
Copy types don't move
Not all types move. Types that implement the Copy trait duplicate their data on assignment instead of moving. Primitive types like i32, u64, bool, f64, and char implement Copy. References (&T) also implement Copy.
fn main() {
// x32 implements Copy.
let x32 = 42;
// Assignment copies the value.
// x32 remains valid.
let y32 = x32;
println!("x32: {}, y32: {}", x32, y32);
}
Copy types live on the stack. Their size is known at compile time. Copying them is as cheap as moving a register. Rust allows implicit copying for these types to keep code ergonomic. You don't need to borrow an integer. You can pass it by value and the caller keeps using it.
Custom types can implement Copy if all their fields are Copy. A struct containing only integers and booleans can derive Copy. A struct containing a String cannot, because String is not Copy.
Use types that implement Copy when you want cheap duplication without explicit cloning.
Decision matrix
Choose the passing strategy based on ownership needs.
Use a reference (&T) when the function only needs to read the data and the caller should keep ownership. Use a mutable reference (&mut T) when the function needs to modify the data in place and the caller retains ownership. Use clone() when the function needs its own independent copy of the data and the caller must also keep the original. Use value passing (move) when the function takes full responsibility for the data and the caller no longer needs it. Use types that implement Copy (like i32, bool, f64) when you want cheap duplication without explicit cloning.
Choose references by default. Move only when you mean it.