How to fix Rust E0382 moved due to use in closure

Fix Rust E0382 by borrowing variables in closures with `&` or using `move` to explicitly transfer ownership.

The closure took your variable. Now what?

You are writing a callback for a button click. You have a String holding the user's name. You pass a closure to button.on_click so the handler can greet the user. The compiler accepts the closure. You try to print the name in the main function right after setting up the handler. Rust rejects the code with E0382 (use of moved value). The variable is gone. The closure has it. You can't use it where it started.

This happens because Rust closures capture their environment. When a closure captures a variable, it takes that variable into its own scope. If the capture takes ownership, the original variable is moved. Once moved, the variable is uninitialized in the outer scope. Accessing it would be undefined behavior, so the compiler blocks you at compile time.

The fix depends on what you need. If the closure only needs to read the data and runs immediately, borrow the variable. If the closure needs to own the data because it outlives the current scope, use shared ownership with Rc or Arc. If you explicitly want the closure to own the data and don't need the original, the error is telling you to stop using the original.

How closures capture data

A closure in Rust is not magic. The compiler turns a closure into a hidden struct. The fields of that struct hold the captured variables. When you write || println!("{}", data), the compiler generates something conceptually similar to this:

struct Closure {
    data: String, // or &String, depending on usage
}

The compiler decides the type of the field based on how the closure uses the variable. If the closure only reads data, the field becomes a reference. If the closure moves data or the closure is marked move, the field becomes an owned String.

This capture mechanism is why E0382 appears. When the closure struct takes ownership of data, the String is moved from the local variable into the closure struct. The local variable is now empty. Rust tracks this state. Any attempt to use the local variable after the move triggers E0382.

Think of the closure as a backpack. When you create the backpack, you decide what goes inside. You can put the actual item in the backpack, or you can put a photograph of the item. If you put the actual item in and walk away, you cannot use the item where you started. The item is in the backpack. Rust forces you to be explicit about whether the backpack holds the item or a photograph.

Minimal example: move versus borrow

The most common trigger for E0382 in closures is the move keyword. The move keyword forces the closure to capture all used variables by value. This is useful for threads and async tasks, but it moves data out of the current scope.

fn main() {
    let data = String::from("secret");

    // The `move` keyword forces ownership transfer.
    // `data` is moved into the closure's hidden struct.
    let closure = move || println!("{}", data);

    closure();

    // E0382: use of moved value: `data`
    // The compiler knows `data` is inside `closure`.
    println!("{}", data);
}

The compiler rejects this with E0382. The variable data was moved into closure. It no longer exists in main.

To fix this when you still need data in the outer scope, remove the move keyword. Rust will infer a borrow instead. The closure captures a reference to data. The reference is cheap to copy. The original String stays in main.

fn main() {
    let data = String::from("secret");

    // No `move`. The compiler infers a borrow.
    // The closure captures `&data`.
    let closure = || println!("{}", data);

    closure();

    // `data` is still valid. The closure only borrowed it.
    println!("{}", data);
}

This works because the closure runs immediately and does not outlive data. The borrow checker verifies that the reference inside the closure is valid for the duration of the closure's use.

Why Rust forces this check

Rust's ownership system prevents use-after-free bugs. If Rust allowed you to use data after moving it into a closure, the closure could hold a reference to freed memory. Or the closure could mutate data that you think is immutable. The E0382 error is the compiler enforcing the invariant that every value has exactly one owner at any time.

When you move a value into a closure, the closure becomes the owner. The outer scope loses ownership. The compiler tracks this transfer. If you try to use the value in the outer scope, the compiler sees that ownership has transferred and rejects the code.

This check happens at compile time. There is no runtime overhead. The compiler analyzes the data flow and inserts the check. If the code compiles, the ownership is valid. You never get a null pointer or a dangling reference from a moved value.

Convention aside: The community treats E0382 as a helpful error, not a nuisance. When you see E0382, ask yourself: "Does this closure need to own the data, or can it borrow?" If it can borrow, remove move. If it must own, you need to restructure the code to share ownership or clone the data before the move.

Realistic example: shared state in a UI handler

Real applications often need closures that outlive the current scope. A UI framework might store a click handler in a widget. The widget lives longer than the function that creates it. The closure must own its captured data, or the data must live long enough.

If you try to move a variable into such a closure and also use it later, you hit E0382. The solution is shared ownership. Wrap the data in Rc for single-threaded code or Arc for multi-threaded code. Clone the smart pointer before moving it into the closure.

use std::rc::Rc;

fn setup_button() {
    // Wrap the shared data in Rc for reference counting.
    let config = Rc::new(String::from("production"));

    // Clone the Rc, not the String.
    // This bumps the reference count. Both `config` and `config_clone`
    // point to the same heap allocation.
    let config_clone = Rc::clone(&config);

    // The closure moves `config_clone` into itself.
    // `config` in the outer scope is still valid.
    let handler = move || {
        println!("Handler config: {}", config_clone);
    };

    // Store the handler somewhere that outlives this function.
    // button.on_click(handler);

    // `config` is still usable here.
    println!("Main config: {}", config);
}

The Rc::clone call does not copy the string. It increments the reference counter. The string data stays on the heap. Both config and config_clone share the data. When the last Rc is dropped, the string is freed.

Convention aside: Use Rc::clone(&rc) instead of rc.clone(). Both compile and work. The explicit form signals to readers that you are cloning the reference count, not deep-copying the data. rc.clone() looks like it might copy the inner value, which can confuse readers expecting a deep clone.

Pitfalls and related errors

E0382 often appears alongside other borrow checker errors. Understanding the interaction helps you fix the root cause.

If you try to mutate a variable inside a closure while also reading it in the outer scope, you might see E0502 (cannot borrow as mutable because it is also borrowed as immutable). The closure captures a mutable reference. The outer scope tries to use the variable. Rust prevents data races and aliasing violations.

fn main() {
    let mut data = String::from("hello");

    // Closure captures `&mut data`.
    let closure = || {
        data.push_str(" world");
    };

    // E0502: cannot borrow `data` as immutable because it is also borrowed as mutable
    println!("{}", data);

    closure();
}

The fix is to ensure the mutable borrow does not overlap with the immutable use. Call the closure before printing, or restructure the code to avoid the overlap.

Another pitfall is using move when you don't need it. Beginners sometimes add move to every closure to silence lifetime errors. This can cause E0382 later when you try to use the captured variable. Only use move when the closure must own the data. If the closure runs immediately and the data lives in the same scope, let the compiler infer the borrow.

E0382 can also appear with FnOnce closures. A FnOnce closure consumes its captured data. If you try to call a FnOnce closure twice, you get E0382 on the closure variable itself. The closure moved the data on the first call. The closure is now empty.

fn main() {
    let data = String::from("once");

    // This closure moves `data`. It is FnOnce.
    let closure = move || println!("{}", data);

    closure();

    // E0382: use of moved value: `closure`
    // The closure consumed `data` on the first call.
    closure();
}

The compiler rejects the second call. FnOnce closures can only be called once. If you need to call the closure multiple times, the closure must capture by reference, or the captured data must implement Copy.

Decision: when to use move, borrow, or shared ownership

Choose the capture strategy based on the lifetime of the closure and the needs of the outer scope.

Use move closures when the closure outlives the current scope, such as in threads, async tasks, or stored callbacks. The closure must own its data because the stack frame may be gone when the closure runs.

Use borrowing closures (no move) when the closure runs immediately and the data lives in the same scope. The compiler infers references automatically. This avoids moving data and keeps the original variable available.

Use Rc::clone or Arc::clone when you need shared ownership across multiple closures or scopes. Wrap the data in a smart pointer. Clone the pointer before moving it into the closure. The original pointer remains valid in the outer scope.

Reach for RefCell or Mutex when you need interior mutability inside a shared closure. If the closure needs to mutate shared data, the data must be wrapped in a type that allows mutation through shared references.

Pick the capture mode that matches the lifetime of your data. If the data dies, the closure dies. If the closure lives, the data must live too. Trust the borrow checker. It usually has a point.

Where to go next