You passed the data, but the compiler says it's gone
You write a function to process a user's input. You create a String, pass it into the function, and the function does its job. You try to log the input afterward for debugging. The compiler refuses to compile with E0382: use of moved value. You didn't delete the variable. You didn't touch it after the call. Why does Rust think the data is gone?
The error means exactly what it says. You used a value after its ownership transferred to another place. In Rust, passing a String to a function doesn't make a copy. It moves the data. Once the function has the String, the original variable is invalid. The compiler stops you from reading memory that no longer belongs to you.
Move semantics: handing over the deed
Rust treats heap-allocated data like a physical document with a single owner. When you assign a String to a new variable or pass it to a function, you aren't photocopying the document. You are handing the document over. The recipient becomes the sole owner. The original variable becomes a placeholder with no data.
Think of it like a signed deed to a house. When you hand the deed to someone else, you no longer own the house. You can't sell it again. You can't live in it. If you try, the law stops you. Rust's compiler enforces this rule at compile time. It prevents you from using a variable after you've handed ownership away.
This rule exists for two reasons. First, performance. If Rust copied heap data on every assignment, every variable binding would allocate memory and copy bytes. That would be slow. Moves are zero-cost. The compiler just copies a few bytes on the stack: a pointer, a length, and a capacity. The heap data stays where it is.
Second, safety. If two variables owned the same heap allocation, both would try to free the memory when they go out of scope. That causes a double-free bug, which corrupts memory and creates security vulnerabilities. Moves ensure only one variable owns the data. Only one variable frees the memory. The compiler guarantees this invariant.
Minimal example: triggering E0382
This code triggers the error. The variable s moves into process. The println tries to use s after the move.
/// Demonstrates the E0382 move error.
fn main() {
// Create a String on the heap. `s` owns the data.
let s = String::from("rustacean");
// Pass ownership of `s` to `process`.
// The variable `s` is now invalid.
process(s);
// ERROR: E0382. `s` was moved into `process`.
// You cannot use it here.
println!("{}", s);
}
/// Takes ownership of the String.
fn process(input: String) {
// `input` owns the String now.
println!("Processing: {}", input);
}
The compiler rejects this with E0382: use of moved value: s. The error points to the println line. It tells you s was moved. The fix depends on what you need. If process only needs to read the data, borrow it. If process needs ownership and you also need the data, clone it. If process needs ownership but you want the data back, return it.
Don't ignore the error. Using a moved value would access freed memory. The compiler is protecting you from undefined behavior.
Why Rust moves by default
Python and JavaScript handle this differently. In Python, s2 = s1 creates a new reference to the same object. Both variables can use the data. Rust doesn't do this for heap types. Rust moves by default.
This design choice keeps Rust fast and safe without a garbage collector. Implicit copying would waste memory and CPU cycles. Implicit shared references would require reference counting or garbage collection, which adds overhead and complexity. Moves give you the performance of manual memory management with the safety of automatic cleanup.
When you see E0382, the compiler is enforcing this design. It's telling you that you're trying to use data after you've given it away. The solution is to be explicit about ownership. Borrow when you don't need ownership. Clone when you need a copy. Return when you want ownership back.
The Copy trait: small data behaves differently
Not all types move. Integers, booleans, and floats copy instead of move. This code compiles without error:
/// Integers implement Copy, so they don't move.
fn main() {
// `x` holds an i32 on the stack.
let x = 42;
// `y` gets a bitwise copy of `x`.
// `x` remains valid.
let y = x;
// Both variables are usable.
println!("x = {}, y = {}", x, y);
}
The difference is the Copy trait. Types that implement Copy are small and live entirely on the stack. Copying them is as cheap as moving them. The compiler generates a bitwise copy instead of a move. The original variable stays valid.
String doesn't implement Copy. It owns heap data. Copying it would require allocating new memory and copying the bytes. That's expensive. The compiler won't do it implicitly. If you want a copy of a String, you must call .clone(). That makes the cost visible in your code.
Trust the compiler here. If a type doesn't implement Copy, it has a reason. Cloning it costs something. Be explicit about the cost.
Realistic scenario: configuration and servers
This pattern shows up in real code. You load configuration, pass it to a server, and try to log it afterward.
/// Realistic scenario: passing config to a server.
struct Config {
port: u16,
host: String,
}
/// Server takes ownership of config.
fn run_server(config: Config) {
// Server owns the config now.
println!("Server running on {}:{}", config.host, config.port);
}
fn main() {
// Create the config.
let config = Config {
port: 8080,
host: String::from("localhost"),
};
// Move config into the server.
run_server(config);
// ERROR: E0382. `config` was moved.
// You can't log it after the server takes it.
println!("Config loaded: {}", config.host);
}
The server needs the config. It stores it or uses it for the lifetime of the program. The main function tries to log the config after passing it. The compiler rejects this with E0382.
The fix depends on the architecture. If the server only needs to read the config, pass a reference. If the server needs ownership and you need to log the config, clone it before passing. If you just want to log before passing, move the log earlier.
Don't fight the ownership model. Design your data flow around moves and borrows. The compiler will guide you to a safe design.
Fixing the error: references, clones, and returns
You have three main ways to fix E0382. Choose based on what your code needs.
Borrow with a reference. If the function only reads the data, change the parameter to a reference. The caller keeps ownership. The function gets temporary access.
/// Fix: borrow the String instead of moving it.
fn process(input: &String) {
// `input` is a reference.
// The caller still owns the data.
println!("Processing: {}", input);
}
fn main() {
let s = String::from("rustacean");
// Pass a reference. `s` is not moved.
process(&s);
// `s` is still valid.
println!("{}", s);
}
The community convention is to take &str instead of &String in function arguments. &str is more flexible. It accepts &String and &str literals. It avoids tying your API to a specific allocation type.
Clone the value. If the function needs ownership and the caller also needs the data, clone it. Cloning creates a deep copy. It allocates new memory and copies the data.
/// Fix: clone the String before moving it.
fn process(input: String) {
// `input` owns the String.
println!("Processing: {}", input);
}
fn main() {
let s = String::from("rustacean");
// Clone `s` to create a second owned copy.
// The clone is moved into `process`.
process(s.clone());
// `s` is still valid.
println!("{}", s);
}
Cloning is expensive. It copies bytes. Use it only when you need a second owned copy. If you clone everything, you lose the performance benefit of moves. Measure the cost before cloning in hot paths.
Return the value. If the function needs ownership temporarily but the caller wants the data back, return it. The function moves the value in, does work, and moves it back out.
/// Fix: return the String to transfer ownership back.
fn process_and_return(mut input: String) -> String {
// `input` owns the String.
// Modify it.
input.push_str(" processed");
// Return it to move ownership back.
input
}
fn main() {
let s = String::from("rustacean");
// Move `s` in, get it back out.
let s = process_and_return(s);
// `s` owns the modified data.
println!("{}", s);
}
This pattern is useful for builders and transformers. The function takes ownership, modifies the data, and returns it. The caller rebinds the variable. Ownership transfers cleanly.
Pitfalls and compiler signals
Watch for these common mistakes.
Over-cloning. Developers new to Rust often clone everything to avoid move errors. That wastes memory and CPU. If a function only reads data, take a reference. If you clone a large String or Vec in a loop, your program slows down. Profile your code. Replace clones with references where possible.
Borrowing when ownership is needed. If you pass a reference to a function that stores the data, you get E0597: borrowed value does not live long enough. The function needs ownership. It can't hold a reference to data that might be dropped. Pass an owned value or clone it.
Partial moves. Some structs allow moving individual fields. This is called a partial move. It's advanced and rare. Stick to full moves and borrows until you understand the basics. Partial moves can make code hard to reason about.
Closures and moves. Closures capture variables by reference by default. If the closure outlives the variable, you need a move closure. That forces the closure to take ownership. This is a different error pattern. Handle it by adding the move keyword to the closure.
The compiler gives precise errors. E0382 means moved. E0597 means borrowed too short. Read the error. It tells you exactly what went wrong. Fix the ownership flow. The code will compile.
Decision matrix: choosing the right approach
Use a reference (&T) when the function only needs to read the data and the caller must keep ownership. References are cheap and flexible. They avoid allocations.
Use .clone() when the function needs ownership and the caller also needs to keep the original data. Cloning creates a deep copy. It costs memory and CPU. Use it sparingly.
Use Copy types when the data is small and stack-allocated, like integers, booleans, or floats. Copy types don't move. They copy implicitly. You don't need to do anything special.
Use return values when the function needs ownership temporarily but the caller wants the data back. Return the value to transfer ownership. This is clean and efficient.
Use Rc<T> when multiple parts of your program need shared ownership without the overhead of cloning large data. Rc adds reference counting. It keeps data alive as long as anyone holds a reference. Use it for graphs and shared state.
Trust the borrow checker. It usually has a point. If the compiler rejects your code, the ownership flow is wrong. Fix the flow. Don't work around it with unsafe code or excessive cloning.