Moving Data into Threads
Use move closures correctly.
Moving Data into Threads is a free Learn Rust Coding lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Learn Rust Coding learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Closures Capture the Environment
A closure passed to thread::spawn can use variables from the surrounding scope. By default Rust borrows them.
But a spawned thread might outlive the function that created it, so a plain borrow is not safe. Rust rejects this at compile time.
The Borrow Problem
If a thread borrows a local variable, the compiler cannot prove the variable lives long enough. The thread could keep running after the variable is dropped.
The code below does not compile, because the closure only borrows data.
use std::thread;
fn main() {
let data = vec![1, 2, 3];
// ERROR: closure may outlive `data`
let h = thread::spawn(|| {
println!("{:?}", data);
});
h.join().unwrap();
}The move Keyword
Adding move before the closure forces it to take ownership of the captured variables. They are moved into the thread.
Now the thread owns the data, so it is guaranteed to stay valid for the thread's lifetime.
use std::thread;
fn main() {
let data = vec![1, 2, 3];
let h = thread::spawn(move || {
println!("{:?}", data);
});
h.join().unwrap();
}Ownership Transfers Out
Once a value is moved into a thread, the original scope can no longer use it. Ownership has moved away.
Trying to use data in main after the move would be a compile error. The thread is now the sole owner.
use std::thread;
fn main() {
let data = vec![1, 2, 3];
let h = thread::spawn(move || println!("{:?}", data));
// println!("{:?}", data); // ERROR: value moved
h.join().unwrap();
}Moving Copy Types
Types that implement Copy, like integers, are copied rather than moved. The closure gets its own copy.
So after a move closure captures a number, you can still use the original in main.
use std::thread;
fn main() {
let n = 42;
let h = thread::spawn(move || {
println!("thread sees {}", n);
});
println!("main still sees {}", n);
h.join().unwrap();
}Send: Safe to Transfer
A type can only be moved into another thread if it implements the Send trait. Send means it is safe to transfer ownership across threads.
Most types are Send automatically. A few, like Rc, are not, and the compiler will reject moving them.
Rc Is Not Send
Rc is a single-threaded reference counter. Its count is not protected against concurrent updates, so it is not Send.
Trying to move an Rc into a thread fails to compile. You will use Arc instead in the next lesson.
use std::rc::Rc;
use std::thread;
fn main() {
let r = Rc::new(5);
// ERROR: `Rc<i32>` cannot be sent between threads safely
let h = thread::spawn(move || println!("{}", r));
h.join().unwrap();
}Moving Multiple Values
A single move closure can capture several variables at once. All of them are moved into the thread.
This is handy when a worker needs both some input and a label.
use std::thread;
fn main() {
let label = String::from("sum");
let nums = vec![1, 2, 3, 4];
let h = thread::spawn(move || {
let total: i32 = nums.iter().sum();
println!("{} = {}", label, total);
});
h.join().unwrap();
}Cloning Before Moving
If both the thread and the main function need a value, clone it first. Give one clone to the thread and keep the other.
Cloning copies the data, so each side owns an independent value.
use std::thread;
fn main() {
let original = String::from("hello");
let for_thread = original.clone();
let h = thread::spawn(move || println!("thread: {}", for_thread));
println!("main: {}", original);
h.join().unwrap();
}Returning Moved Data Back
A thread that owns moved data can return it, handing ownership back to the parent through join.
This pattern moves data in, processes it, then moves the result out.
use std::thread;
fn main() {
let mut v = vec![3, 1, 2];
let h = thread::spawn(move || {
v.sort();
v
});
let sorted = h.join().unwrap();
println!("{:?}", sorted);
}Why move Is Required
Without move, the borrow checker assumes the closure borrows. Because spawned threads have no fixed lifetime bound to the caller, borrows are unsafe.
The move keyword converts borrows into ownership transfers, satisfying the 'static requirement of spawn.
Quick Check
Test your understanding of moving data into threads.
Recap
You learned that thread closures must own the data they use, achieved with the move keyword.
Moving transfers ownership, while Copy types are copied. The Send trait marks what is safe to transfer, and Rc is not Send.
Next you will share data between threads using Arc and Mutex.
Frequently asked questions
Is the “Moving Data into Threads” lesson free?
Yes — the full text of “Moving Data into Threads” is free to read here on the web, and the Learn Rust Coding course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Learn Rust Coding course, upgrade to CoddyKit PRO.
What will I learn in “Moving Data into Threads”?
Use move closures correctly. You practise Learn Rust Coding with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Learn Rust Coding?
No prior experience is required. Learn Rust Coding on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Moving Data into Threads” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Learn Rust Coding lesson?
Yes. Every Learn Rust Coding lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Spawning Threads
- Moving Data into Threads
- Sharing with Arc and Mutex
- Joining and Collecting Results