Fix Rust error[E0382]: use of moved value After Ownership Transfer

beginner🦀 Rust2026-03-25| Rust 1.40+, all platforms (Linux, macOS, Windows), rustc compiler

Error Message

error[E0382]: use of moved value: `variable_name`
#rust#ownership#move#borrow-checker#e0382

What Just Happened

You're compiling a Rust program and suddenly see this:

error[E0382]: use of moved value: `name`
  --> src/main.rs:6:20
   |
4  |     let name = String::from("Alice");
5  |     greet(name);
   |           ---- value moved here
6  |     println!("{}", name);
   |                    ^^^^ value used here after move

Rust's ownership model is biting you here. Passing name to greet() transferred ownership into that function. Once that happened, name was gone from the caller's scope — the compiler won't let you touch a variable it no longer owns.

Not necessarily a logic error. The compiler is enforcing memory safety, which is the whole point. The right fix depends on what you actually need to do with that value after the call.

Reproduce the Error

fn greet(name: String) {
    println!("Hello, {}!", name);
}

fn main() {
    let name = String::from("Alice");
    greet(name);           // ownership moves into greet()
    println!("{}", name);  // ERROR: value used after move
}

Run cargo build and you'll get the full E0382 error with exact line numbers pointing to both where the value moved and where you tried to use it again.

Diagnose Which Case You're In

Pick the right fix by answering what you actually need from the value after it's been passed:

  • Only need to read the value afterward? → Use a reference (&)
  • Need an independent copy of the data? → Clone or copy it
  • Want the function to hand ownership back? → Return the value
  • Moving into a loop or closure? → Clone before capture, or restructure

Solution 1: Pass a Reference Instead of the Value

Nine times out of ten, this is the right fix. Change the function to borrow the value instead of taking ownership.

fn greet(name: &str) {  // borrow a string slice instead of owning String
    println!("Hello, {}!", name);
}

fn main() {
    let name = String::from("Alice");
    greet(&name);          // lend it to greet()
    println!("{}", name);  // still valid — we still own it
}

The & operator creates a reference. The function borrows the value temporarily, and that borrow is released when the function returns. You keep ownership throughout.

If your function currently takes String, switching to &str is almost always the right call — unless the function needs to store or extend the string internally.

Solution 2: Clone the Value

When you genuinely need the function to own the data and you need the original intact afterward, clone it:

fn greet(name: String) {
    println!("Hello, {}!", name);
}

fn main() {
    let name = String::from("Alice");
    greet(name.clone());   // give greet() its own copy
    println!("{}", name);  // original still valid
}

Cloning allocates a fresh copy on the heap. It's the blunt solution — correct, but not free. Avoid it inside hot loops or with large data structures. A 1 MB Vec cloned on every iteration adds up fast.

Solution 3: Use Copy Types

Primitives like i32, f64, bool, and char implement the Copy trait. They don't move — they copy automatically, no explicit clone needed.

fn double(x: i32) -> i32 {
    x * 2
}

fn main() {
    let n = 5;
    let result = double(n);
    println!("{} doubled is {}", n, result);  // fine, i32 is Copy
}

Your own structs can get this behavior too, as long as every field is also Copy:

#[derive(Copy, Clone)]
struct Point {
    x: f64,
    y: f64,
}

fn translate(p: Point, dx: f64) -> Point {
    Point { x: p.x + dx, y: p.y }
}

fn main() {
    let p = Point { x: 1.0, y: 2.0 };
    let q = translate(p, 5.0);
    println!("original x: {}", p.x);  // fine — Point is Copy
}

One catch: you can't derive Copy if any field holds a String, Vec, or other heap-allocated type.

Solution 4: Move Into a Loop or Closure

Closures are where E0382 surprises people most. The move keyword captures ownership — and once captured, the original is gone:

// Problem: closure takes ownership
let msg = String::from("hello");
let print_msg = move || println!("{}", msg);
print_msg();
println!("{}", msg);  // ERROR: moved into closure

// Fix: clone before the closure captures it
let msg = String::from("hello");
let msg_clone = msg.clone();
let print_msg = move || println!("{}", msg_clone);
print_msg();
println!("{}", msg);  // original still here

Inside regular for loops, this is less of an issue. The println! macro borrows implicitly, so iterating over &items while referencing msg works without any cloning.

Solution 5: Return Ownership Back

Occasionally a function needs ownership temporarily and you want it back when it's done:

fn process(data: String) -> String {
    println!("Processing: {}", data);
    data  // return ownership to caller
}

fn main() {
    let s = String::from("some data");
    let s = process(s);  // rebind with returned value
    println!("Got back: {}", s);
}

Borrowing is almost always cleaner, but this pattern earns its keep when the function transforms or extends the value — think builders and string formatters.

Verify the Fix

Rebuild and check the output:

cargo build

Zero E0382 errors means the ownership issue is resolved. Go one step further to make sure the fix makes semantic sense:

cargo test
cargo clippy   # catches patterns that could be simplified further

If Clippy flags your function signatures and suggests &str over String, that's a signal you're cloning where a borrow would do.

Quick Decision Guide

  • Function only reads the data → change parameter to &T or &str
  • Function stores the data (e.g., in a struct field) → ownership transfer is intentional, leave it
  • Need both original and a modified copy.clone() before passing
  • Data is a primitive or stack-only struct → derive or rely on Copy
  • Used inside a move closure → clone the value before the closure captures it

What to Remember

E0382 isn't the compiler being pedantic — it's catching a real bug before it ships. The same pattern in C++ compiles fine and causes a use-after-free at runtime. Rust surfaces it at compile time instead, with line numbers and a clear message.

Start with borrowing. Change fn foo(s: String) to fn foo(s: &str) first — it's more flexible, skips the allocation, and resolves the error in most cases. Reach for .clone() only after you've ruled out a reference.

Related Error Notes