0Pricing
Learn Rust Coding · Lesson

Joining and Collecting Results

Wait for threads and gather output.

Joining and Collecting Results is a free Learn Rust Coding lesson on CoddyKit — lesson 4 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.

Why Join Threads?

Spawning starts work, but you usually need its result or at least proof that it finished. Joining is how you wait.

In this lesson you will collect handles, join them, and gather the values they produce into a final answer.

join Returns a Result

join returns a Result. On success it holds the closure's return value; on a thread panic it holds an Err.

Calling unwrap gives the value directly, but propagates a panic if the thread failed.

use std::thread;

fn main() {
    let h = thread::spawn(|| 7 * 6);
    let value: i32 = h.join().unwrap();
    println!("answer = {}", value);
}

Collecting Handles First

To run threads in parallel, spawn them all before joining any. Push each handle into a vector as you go.

If you join inside the spawn loop, each thread finishes before the next starts, and you lose concurrency.

use std::thread;

fn main() {
    let handles: Vec<_> = (0..4)
        .map(|i| thread::spawn(move || i * i))
        .collect();
    for h in handles {
        println!("{}", h.join().unwrap());
    }
}

Collecting Results into a Vec

You can join every handle and push the returned values into a results vector.

Spawn order and join order both run zero to three here, so the results vector ends up in a predictable order.

use std::thread;

fn main() {
    let handles: Vec<_> = (1..=4)
        .map(|n| thread::spawn(move || n * 10))
        .collect();
    let results: Vec<i32> = handles
        .into_iter()
        .map(|h| h.join().unwrap())
        .collect();
    println!("{:?}", results);
}

Summing Parallel Results

Once you have collected results, you can reduce them. Here each thread computes a partial value, and main sums them.

This map then reduce shape is a common parallel pattern.

use std::thread;

fn main() {
    let handles: Vec<_> = (1..=5)
        .map(|n| thread::spawn(move || n * n))
        .collect();
    let total: i32 = handles
        .into_iter()
        .map(|h| h.join().unwrap())
        .sum();
    println!("total = {}", total);
}

Splitting Work into Chunks

For larger data, split it into chunks and give each chunk to its own thread. Each thread returns a partial sum.

Cloning each chunk into an owned Vec lets the thread own its slice safely.

use std::thread;

fn main() {
    let data: Vec<i32> = (1..=10).collect();
    let handles: Vec<_> = data
        .chunks(5)
        .map(|c| { let c = c.to_vec(); thread::spawn(move || c.iter().sum::<i32>()) })
        .collect();
    let total: i32 = handles.into_iter().map(|h| h.join().unwrap()).sum();
    println!("{}", total);
}

Handling a Thread Panic

If a worker might panic, do not blindly unwrap. Match on the Result from join to handle the error path.

This keeps the main thread alive even when one worker fails.

use std::thread;

fn main() {
    let h = thread::spawn(|| {
        if true { panic!("failed"); }
        1
    });
    match h.join() {
        Ok(v) => println!("got {}", v),
        Err(_) => println!("thread panicked"),
    }
}

Collecting into a Shared Vec

Instead of returning values, threads can push into a shared Arc<Mutex<Vec>>. Order is then nondeterministic.

After joining all threads, lock the vector once to read every collected result.

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

fn main() {
    let out = Arc::new(Mutex::new(vec![]));
    let mut handles = vec![];
    for i in 0..3 {
        let o = Arc::clone(&out);
        handles.push(thread::spawn(move || o.lock().unwrap().push(i)));
    }
    for h in handles { h.join().unwrap(); }
    println!("{:?}", out.lock().unwrap());
}

Scoped Threads

thread::scope lets threads borrow local data without move or Arc. The scope guarantees all threads finish before it returns.

This makes parallel reads of stack data ergonomic and safe.

use std::thread;

fn main() {
    let data = vec![1, 2, 3];
    thread::scope(|s| {
        s.spawn(|| println!("sum {}", data.iter().sum::<i32>()));
        s.spawn(|| println!("len {}", data.len()));
    });
}

Returning Results from Scope

Scoped threads also return values through their handles. You join them inside the scope to gather results.

Because the scope blocks until completion, borrowed data stays valid the whole time.

use std::thread;

fn main() {
    let nums = vec![4, 5, 6];
    let total = thread::scope(|s| {
        let h = s.spawn(|| nums.iter().sum::<i32>());
        h.join().unwrap()
    });
    println!("total = {}", total);
}

Don't Forget to Join

If you drop a JoinHandle without joining, the thread becomes detached. It keeps running, but you lose its result and any way to wait for it.

The process may also exit before a detached thread finishes. Join when you need the result or completion.

Quick Check

Test your understanding of joining and collecting results.

Recap

You learned that join returns a Result carrying a thread's value or panic. Spawn all threads first, then join to keep concurrency.

You can collect results into a Vec, reduce them, or push into a shared Arc<Mutex<Vec>>. Scoped threads borrow local data safely, and dropping a handle detaches the thread.

You now have the core tools for threads and shared state in Rust.

Frequently asked questions

Is the “Joining and Collecting Results” lesson free?

Yes — the full text of “Joining and Collecting Results” 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 “Joining and Collecting Results”?

Wait for threads and gather output. 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 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Joining and Collecting Results” 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

  1. Spawning Threads
  2. Moving Data into Threads
  3. Sharing with Arc and Mutex
  4. Joining and Collecting Results
← Back to Learn Rust Coding