| |

Rust 50 🦀 Moving Environment Variables into Closures (move keyword)

A closure captures the variables it uses from the surrounding scope. By default, the compiler infers the least restrictive capture: an immutable borrow if the closure only reads the variable, a mutable borrow if it modifies it, and a move if the closure takes ownership. The inference is convenient for the closures that are called immediately and discarded. But when the closure outlives the scope where it was defined — a thread, an async task, a callback stored in a struct — the borrow is not enough. The closure must own its data. The move keyword forces the capture by value, so the closure takes the ownership of every variable it uses, and the closure can be sent anywhere the owned data can go.

The move keyword does not change what the closure does. It changes how the closure captures. A closure that reads a variable and is marked move still reads the variable; it just owns the copy instead of borrowing the original. A closure that modifies a variable and is marked move still modifies it; it just modifies its own copy. The behavior is the same, and the ownership is different. The distinction matters when the closure is sent to another thread, stored in a struct, or returned from a function, because the closure must not hold the reference to the data that may be dropped.

The interaction with the Copy and the Clone traits is the subtlety. The move moves the value, and the value is no longer available in the original scope. For the Copy types — the i32, the bool, the f64, the &T — the move is the copy, and the original is still usable. For the non-Copy types — the String, the Vec<T>, the Box<T> — the move transfers the ownership, and the original is not usable. The developer must clone the value before the move if the original is still needed, or restructure the code so the closure owns a separate copy.

This chapter covers three areas. First, why the move keyword exists — the outliving-closure problem and the thread-safety requirement. Second, how the move works — the syntax, the Copy versus the non-Copy, the partial move, and the interaction with the borrow checker. Third, the patterns and the pitfalls — the thread spawning, the async tasks, the returned closures, the Arc sharing, and the cases where the move is not the right tool. The chapter ends with a complete example session, a quick reference, best practices, common pitfalls, real-world examples, and diagrams showing the capture modes.

Key point: The move keyword forces the closure to capture its environment by value. The closure takes the ownership of every variable it uses, so the closure can outlive the scope where it was defined. For the Copy types, the move is the copy, and the original is still usable. For the non-Copy types, the move transfers the ownership, and the original is not usable. The move is the requirement for the threads, the async tasks, and the callbacks stored in the structs.


Why the move keyword exists

The outliving-closure problem. A closure that borrows a variable is valid only while the variable is alive. The closure cannot outlive the scope where the variable is defined, because the borrow would dangle. A thread, an async task, and a callback stored in a struct all outlive the scope where they are created. They cannot hold the borrow, because the borrowed data may be dropped before the closure runs. The move solves the problem by transferring the ownership, so the closure owns the data and can outlive the original scope.

The thread-safety problem. The thread::spawn requires the closure to be the 'static and the Send. The 'static means the closure contains no references with the shorter lifetime, and the Send means the closure can be transferred to another thread. The move is the requirement for the 'static: the closure owns the data, so it contains no references. The Send is the property of the owned data, and the compiler checks it.

The async problem. The async block and the tokio::spawn have the same requirement. The async task can be suspended and resumed on the different threads, and the task must own the data it uses across the await points. The move on the async block moves the captured variables into the future, and the future owns them.

The callback problem. The struct that stores the callback must own the callback, and the callback must own its data. The Box<dyn Fn() + Send + 'static> is the common type, and the closure inside must be the move. The 'static bound requires the owned data, and the Send requires the thread-safe data.

The partial-move problem. The move moves the entire variable, but the closure may use only the field of the struct. The compiler tracks the partial move, and the closure owns the field, and the rest of the struct is still usable. The pattern is the useful for the structs with the mixed lifetimes.

The trade-off. The move transfers the ownership, and the original is not usable for the non-Copy types. The developer must clone the value, restructure the code, or use the Arc for the shared ownership. The trade-off is between the simplicity of the move and the cost of the clone or the atomic reference counting.


a. The move syntax and the Copy types

The move keyword is placed before the parameter list: move |params| body.

let name = String::from("Alice");
let greet = move || println!("Hello, {}", name);

greet();
// println!("{}", name); // the error: the name is moved

The move moves the name into the closure. The greet owns the name, and the original is not usable. The closure is the Fn, because it only reads the name, but the name is moved.

The Copy types. The Copy types are the i32, the u32, the f64, the bool, the char, the &T, and the tuples of the Copy types. The move on the Copy type is the copy, and the original is still usable.

let x = 5;
let add = move |y| x + y;

println!("{}", add(10)); // 15
println!("{}", x);        // 5, the x is still available

The move copies the x into the closure, and the x is the i32, which is the Copy. The original x is still usable.

The non-Copy types. The String, the Vec<T>, the Box<T>, and the structs without the Copy derive are the non-Copy. The move transfers the ownership, and the original is not usable.

let name = String::from("Alice");
let greet = move || println!("Hello, {}", name);

greet();
// println!("{}", name); // the error: the value was moved

The name is the String, which is the non-Copy. The move transfers the ownership to the closure, and the original is not usable.

The clone before the move. The original is still needed, and the clone is the fix:

let name = String::from("Alice");
let name_clone = name.clone();
let greet = move || println!("Hello, {}", name_clone);

greet();
println!("{}", name); // the name is still available

The name_clone is the separate String, and the closure owns the clone. The original name is still usable.

The partial move. The closure uses the field of the struct, and the field is moved, and the rest of the struct is still usable.

struct Person {
    name: String,
    age: u32,
}

let person = Person {
    name: String::from("Alice"),
    age: 30,
};

let greet = move || println!("Hello, {}", person.name);

greet();
println!("{}", person.age); // the age is still available
// println!("{}", person.name); // the error: the name was moved

The person.name is moved into the closure, and the person.age is still available. The partial move is the useful for the structs with the mixed fields.


b. The move with the threads

The thread::spawn is the classic use case. The thread outlives the scope where it is created, and the closure must own the data.

use std::thread;

fn main() {
    let name = String::from("Alice");

    let handle = thread::spawn(move || {
        println!("Hello, {}", name);
    });

    handle.join().unwrap();
}

The move moves the name into the closure, and the thread owns the data. The main cannot use the name after the spawn, because the ownership is transferred.

The move with the loop. The loop variable must be moved for each iteration:

use std::thread;

fn main() {
    let mut handles = vec![];

    for i in 0..5 {
        let handle = thread::spawn(move || {
            println!("Thread {}", i);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

The move captures the i by the value for each iteration, and the threads print the 0 to the 4. The i is the i32, which is the Copy, so the loop variable is copied, and the next iteration uses the new value.

The move with the non-Copy loop variable. The String in the loop must be moved, and the clone is the pattern if the original is needed:

use std::thread;

fn main() {
    let names = vec!["Alice", "Bob", "Carol"];
    let mut handles = vec![];

    for name in names {
        let handle = thread::spawn(move || {
            println!("Hello, {}", name);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

The for name in names takes the ownership of each &str, and the move moves it into the closure. The names is the Vec<&str>, and the iteration consumes it.

The move with the shared state. The Arc shares the ownership across the threads:

use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3, 4, 5]);
    let mut handles = vec![];

    for i in 0..3 {
        let data = Arc::clone(&data);
        let handle = thread::spawn(move || {
            let sum: i32 = data.iter().sum();
            println!("Thread {}: the sum is {}", i, sum);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

The Arc::clone creates the new reference to the shared data, and the move moves the Arc clone into the closure. Each thread owns the Arc, and the data is shared.

The move with the mutable shared state. The Arc<Mutex<T>> shares the mutable state:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Result: {}", *counter.lock().unwrap()); // 10
}

The Arc<Mutex<i32>> shares the mutable state, and the move moves the Arc clone into each thread. The Mutex ensures the exclusive access.


c. The move with the async and the returned closures

The async block. The async move block moves the captured variables into the future:

async fn fetch_data() -> String {
    // ...
}

async fn process() {
    let name = String::from("Alice");

    let future = async move {
        let data = fetch_data().await;
        println!("{}: {}", name, data);
    };

    future.await;
}

The async move moves the name into the future, and the future owns it. The future can be sent to the tokio::spawn, and the task owns the data.

The tokio::spawn. The async task requires the move:

#[tokio::main]
async fn main() {
    let name = String::from("Alice");

    let handle = tokio::spawn(async move {
        println!("Hello, {}", name);
    });

    handle.await.unwrap();
}

The async move moves the name into the task, and the task owns the data. The task can run on the different threads, and the data is owned.

The returned closure. The function that returns the closure uses the move:

fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

fn make_counter() -> impl FnMut() -> i32 {
    let mut count = 0;
    move || {
        count += 1;
        count
    }
}

The move |y| x + y captures the x by the value, and the returned closure owns the x. The make_counter captures the count by the value, and the returned closure owns the count. The move is the requirement for the returned closures, because the closure must outlive the function scope.

The Box<dyn Fn> return. The different closures are returned as the boxed trait objects:

fn make_greeter(style: &str) -> Box<dyn Fn(&str)> {
    match style {
        "formal" => Box::new(move |name| println!("Good day, {}", name)),
        _ => Box::new(move |name| println!("Hey, {}", name)),
    }
}

The move captures the style if the closure uses it, and the Box<dyn Fn> allows the different closures. The returned closure owns its data, and the caller can use it after the function returns.

The move with the struct field. The struct that stores the closure needs the move:

struct Handler {
    callback: Box<dyn Fn(i32) -> i32 + Send + 'static>,
}

impl Handler {
    fn new<F>(f: F) -> Self
    where
        F: Fn(i32) -> i32 + Send + 'static,
    {
        Handler {
            callback: Box::new(f),
        }
    }

    fn call(&self, x: i32) -> i32 {
        (self.callback)(x)
    }
}

fn main() {
    let multiplier = 2;
    let handler = Handler::new(move |x| x * multiplier);
    println!("{}", handler.call(5)); // 10
}

The move captures the multiplier by the value, and the closure is the 'static. The struct stores the boxed closure, and the call invokes it.


Complete Example Session

// ============================================
// PART 1: THE BASIC MOVE
// ============================================

fn main() {
    let name = String::from("Alice");
    let greet = move || println!("Hello, {}", name);

    greet();
    // println!("{}", name); // the error: the name is moved
}


// ============================================
// PART 2: THE COPY TYPE
// ============================================

fn main() {
    let x = 5;
    let add = move |y| x + y;

    println!("{}", add(10)); // 15
    println!("{}", x);        // 5, the x is still available
}

// The i32 is the Copy, so the move is the copy.


// ============================================
// PART 3: THE NON-COPY TYPE
// ============================================

fn main() {
    let name = String::from("Alice");
    let greet = move || println!("Hello, {}", name);

    greet();
    // println!("{}", name); // the error
}

// The String is the non-Copy, so the move transfers the ownership.


// ============================================
// PART 4: THE CLONE
// ============================================

fn main() {
    let name = String::from("Alice");
    let name_clone = name.clone();
    let greet = move || println!("Hello, {}", name_clone);

    greet();
    println!("{}", name); // the name is still available
}


// ============================================
// PART 5: THE PARTIAL MOVE
// ============================================

struct Person {
    name: String,
    age: u32,
}

fn main() {
    let person = Person {
        name: String::from("Alice"),
        age: 30,
    };

    let greet = move || println!("Hello, {}", person.name);

    greet();
    println!("{}", person.age); // the age is still available
    // println!("{}", person.name); // the error
}


// ============================================
// PART 6: THE THREAD
// ============================================

use std::thread;

fn main() {
    let name = String::from("Alice");

    let handle = thread::spawn(move || {
        println!("Hello, {}", name);
    });

    handle.join().unwrap();
}


// ============================================
// PART 7: THE THREAD WITH THE LOOP
// ============================================

use std::thread;

fn main() {
    let mut handles = vec![];

    for i in 0..5 {
        let handle = thread::spawn(move || {
            println!("Thread {}", i);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}


// ============================================
// PART 8: THE ARC SHARING
// ============================================

use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3, 4, 5]);
    let mut handles = vec![];

    for i in 0..3 {
        let data = Arc::clone(&data);
        let handle = thread::spawn(move || {
            let sum: i32 = data.iter().sum();
            println!("Thread {}: the sum is {}", i, sum);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}


// ============================================
// PART 9: THE RETURNED CLOSURE
// ============================================

fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    move |y| x + y
}

fn make_counter() -> impl FnMut() -> i32 {
    let mut count = 0;
    move || {
        count += 1;
        count
    }
}

fn main() {
    let add_five = make_adder(5);
    println!("{}", add_five(10)); // 15

    let mut counter = make_counter();
    println!("{}", counter()); // 1
    println!("{}", counter()); // 2
    println!("{}", counter()); // 3
}


// ============================================
// PART 10: THE COMPLETE EXAMPLE
// ============================================

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    // The Arc<Mutex> for the shared mutable state
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Final count: {}", *counter.lock().unwrap()); // 10

    // The returned closure
    let make_adder = |x: i32| move |y: i32| x + y;
    let add_ten = make_adder(10);
    println!("10 + 5 = {}", add_ten(5)); // 15

    // The move with the non-Copy
    let message = String::from("Hello");
    let print_message = move || println!("{}", message);
    print_message();
    // message is moved into the closure

    // The move with the Copy
    let number = 42;
    let print_number = move || println!("{}", number);
    print_number();
    println!("{}", number); // the number is still available
}

The ten parts show the basic move, the Copy type, the non-Copy type, the clone, the partial move, the thread, the thread with the loop, the Arc sharing, the returned closure, and the complete example.


Quick Reference

The move Syntax

SyntaxMeaning
move || bodyThe no parameters
move |x| x + 1The single parameter
move |a, b| a + bThe multiple parameters
async move { ... }The async block

The Copy vs the Non-Copy

TypeThe CopyThe move behavior
i32, u32, f64YesThe copy, the original usable
bool, charYesThe copy
&TYesThe copy
StringNoThe ownership transfer
Vec<T>NoThe ownership transfer
Box<T>NoThe ownership transfer
The struct without the CopyNoThe ownership transfer

The Use Cases

The use caseThe requirement
thread::spawnThe move + the 'static + the Send
tokio::spawnThe async move + the 'static + the Send
The returned closureThe move
The struct fieldThe move + the 'static
The Box<dyn Fn>The move

The Sharing

The typeThe purpose
Arc<T>The shared immutable ownership
Arc<Mutex<T>>The shared mutable ownership
Arc<RwLock<T>>The shared read-write ownership
Rc<T>The single-thread shared ownership

The Partial Move

The exampleThe result
move || person.nameThe name is moved, the age is usable
move || person.ageThe age is copied, the name is usable
move || personThe whole struct is moved

Best Practices

✅ Do This:

// Use the move for the threads
thread::spawn(move || { ... });                                           // ✅
// Use the async move for the async tasks
tokio::spawn(async move { ... });                                         // ✅
// Use the move for the returned closures
fn make_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y }           // ✅
// Use the Arc for the shared ownership
let data = Arc::clone(&shared);                                           // ✅
// Use the Arc<Mutex> for the shared mutable
let counter = Arc::clone(&counter);                                       // ✅
// Use the clone before the move when the original is needed
let name_clone = name.clone();                                            // ✅

❌ Don’t Do This:

// Don't forget the move in the thread
thread::spawn(|| { ... }); // the borrow error                            // ❌
// Don't use the moved value after the closure
let greet = move || println!("{}", name); greet(); println!("{}", name); // ❌
// Don't clone unnecessarily
let name_clone = name.clone(); // if the original is not needed           // ❌
// Don't use the Rc across the threads
let data = Rc::new(vec![1, 2, 3]); thread::spawn(move || { ... });       // ❌
// Don't forget the 'static bound
fn store(f: impl Fn()) { ... } // the missing 'static                    // ❌
// Don't use the move when the borrow suffices
let double = move |x| x * 2; // the unnecessary for the Copy              // ✅ (works but verbose)

Common Pitfalls

PitfallWhy It HappensFix
The moved value usedThe moveClone or restructure
The thread borrow errorThe missing moveAdd the move
The Rc not SendThe single-threadUse the Arc
The returned closure borrowsThe missing moveAdd the move
The struct field borrow errorThe missing moveAdd the move
The partial move confusionThe field accessCheck the moved fields
The clone overheadThe unnecessary cloneRestructure the code

Real-World Examples

1. The Basic Move

let greet = move || println!("{}", name);

2. The Thread

thread::spawn(move || { ... });

3. The Async

tokio::spawn(async move { ... });

4. The Returned Closure

fn make_adder(x: i32) -> impl Fn(i32) -> i32 { move |y| x + y }

5. The Arc

let data = Arc::clone(&shared);

6. The Arc Mutex

let counter = Arc::clone(&counter);

7. The Partial Move

let greet = move || println!("{}", person.name);

8. The Clone

let name_clone = name.clone();

9. The Loop

for i in 0..5 { thread::spawn(move || println!("{}", i)); }

10. The Complete

let counter = Arc::new(Mutex::new(0));
thread::spawn(move || { *counter.lock().unwrap() += 1; });

Visual

The Move vs the Borrow

┌──────────────────────────────────────────────────────────────┐
│  THE MOVE vs THE BORROW                                      │
│                                                              │
│  THE BORROW (the default):                                   │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  let name = String::from("Alice");                   │    │
│  │  let greet = || println!("{}", name);                │    │
│  │                                                       │    │
│  │  The name is borrowed.                               │    │
│  │  The closure cannot outlive the name.                │    │
│  │  The name is usable after the closure.               │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  THE MOVE:                                                   │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  let name = String::from("Alice");                   │    │
│  │  let greet = move || println!("{}", name);           │    │
│  │                                                       │    │
│  │  The name is moved into the closure.                 │    │
│  │  The closure owns the name.                          │    │
│  │  The closure can outlive the scope.                  │    │
│  │  The name is NOT usable after the closure.           │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The Copy vs the Non-Copy

┌──────────────────────────────────────────────────────────────┐
│  THE COPY vs THE NON-COPY                                    │
│                                                              │
│  THE COPY:                                                   │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  let x = 5;  // the i32, the Copy                    │    │
│  │  let add = move |y| x + y;                           │    │
│  │                                                       │    │
│  │  The x is COPIED into the closure.                   │    │
│  │  The original x is still available.                  │    │
│  │                                                       │    │
│  │  println!("{}", add(10)); // 15                      │    │
│  │  println!("{}", x);       // 5                       │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  THE NON-COPY:                                               │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  let name = String::from("Alice");                   │    │
│  │  let greet = move || println!("{}", name);           │    │
│  │                                                       │    │
│  │  The name is MOVED into the closure.                 │    │
│  │  The original name is NOT available.                 │    │
│  │                                                       │    │
│  │  greet();                                             │    │
│  │  // println!("{}", name); // the error               │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The Thread with the Move

┌──────────────────────────────────────────────────────────────┐
│  THE THREAD WITH THE MOVE                                    │
│                                                              │
│  WITHOUT the move:                                           │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  let name = String::from("Alice");                   │    │
│  │  thread::spawn(|| {                                  │    │
│  │    println!("{}", name);                             │    │
│  │  });                                                 │    │
│  │                                                       │    │
│  │  ERROR: the closure may outlive the current function,│    │
│  │  but it borrows the name.                            │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
│  WITH the move:                                              │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  let name = String::from("Alice");                   │    │
│  │  thread::spawn(move || {                             │    │
│  │    println!("{}", name);                             │    │
│  │  });                                                 │    │
│  │                                                       │    │
│  │  The closure owns the name.                          │    │
│  │  The thread can outlive the main.                    │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

The Arc Sharing

┌──────────────────────────────────────────────────────────────┐
│  THE ARC SHARING                                             │
│                                                              │
│  let data = Arc::new(vec![1, 2, 3, 4, 5]);                   │
│                                                              │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  The Arc (the atomic reference count)                │    │
│  │    ├── the strong count: 1                           │    │
│  │    └── the data: the vec                             │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    │  Arc::clone                                          │
│    ▼                                                         │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  The clone                                           │    │
│  │    ├── the strong count: 2                           │    │
│  │    └── the data: the same vec                        │    │
│  └──────────────────────────────────────────────────────┘    │
│    │                                                         │
│    │  move into the thread                                │
│    ▼                                                         │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  The thread owns the Arc clone.                      │    │
│  │  The data is shared.                                 │    │
│  │  The count is decremented on the drop.               │    │
│  └──────────────────────────────────────────────────────┘    │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Summary

ItemValue
moveThe force the capture by value
The Copy typeThe move is the copy
The non-Copy typeThe ownership transfer
The partial moveThe field is moved
The threadThe move + the 'static
The asyncThe async move
The returned closureThe move
The ArcThe shared ownership
The Arc<Mutex>The shared mutable
The cloneThe separate copy

Key takeaways:

  • The move keyword forces the closure to capture the environment by value. The closure takes the ownership of every variable it uses, so the closure can outlive the scope where it was defined. The move is the requirement for the threads, the async tasks, and the callbacks stored in the structs .
  • The Copy types are copied, and the non-Copy types are moved. The i32, the bool, and the &T are the Copy, and the move copies them, so the original is still usable. The String, the Vec<T>, and the Box<T> are the non-Copy, and the move transfers the ownership .
  • The partial move moves the field, not the whole struct. The closure that uses the person.name moves only the name, and the person.age is still available. The compiler tracks the partial moves, and the rest of the struct is usable .
  • The move with the threads. The thread::spawn requires the 'static and the Send, and the move is the requirement for the 'static. The closure owns the data, and the data is the thread-safe .
  • The move with the async. The async move block moves the captured variables into the future, and the future owns them. The tokio::spawn requires the move, and the task owns the data across the await points .
  • The move with the returned closures. The function that returns the closure uses the move, because the closure must outlive the function scope. The impl Fn or the Box<dyn Fn> is the return type .
  • The Arc for the shared ownership. The Arc::clone creates the new reference to the shared data, and the move moves the clone into the closure. The Arc<Mutex<T>> shares the mutable state .
  • The clone before the move. The original is still needed, and the clone is the fix. The clone creates the separate copy, and the closure owns the clone. The trade-off is between the simplicity of the move and the cost of the clone .

Remember: The move keyword forces the closure to capture its environment by value. The closure owns the data, and it can outlive the scope where it was defined. The Copy types are copied, and the non-Copy types are moved. The partial move moves the field, not the whole struct. The Arc shares the ownership, and the Arc<Mutex> shares the mutable state. The result is the closures that can be sent to the threads and the async tasks, which is the foundation of the concurrent Rust.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!