Thread Basics

Back

Loading concept...

🧵 Rust Threads: Your Team of Helper Workers

The Big Idea: Imagine you’re building a LEGO castle. What if you had friends helping you—one building the tower, another the walls, another the gate—all at the same time? That’s what threads do in Rust! They’re like helper workers that do tasks together, making everything faster.


🎬 The Story: A Pizza Party Kitchen

Picture a pizza shop called Rusty’s Pizzeria. The owner (that’s your main program) needs to make 10 pizzas for a party. Doing it alone takes forever! So, the owner hires helper chefs (threads). Each chef works on their own pizza at the same time.

But here’s the tricky part: How do you make sure:

  • No chef leaves early before finishing?
  • Chefs can use shared ingredients safely?
  • Everyone cleans up properly?

That’s exactly what Rust threads solve! Let’s learn how.


1️⃣ Creating Threads

What Is It?

A thread is a mini-worker inside your program. Your main program is one worker. You can create MORE workers to do tasks at the same time.

The Simple Analogy

Think of your program as a kitchen. The main chef (main thread) is cooking. But you can call in extra chefs (new threads) to help!

How to Create a Thread

use std::thread;

fn main() {
    // Hiring a helper chef!
    thread::spawn(|| {
        println!("Helper: I'm making salad!");
    });

    println!("Main chef: I'm making soup!");
}

What Happens Here?

  1. thread::spawn creates a new worker
  2. The || { } is called a closure—it’s the job you give the worker
  3. Both workers run at the same time!

⚠️ The Problem

Sometimes the main chef finishes and closes the kitchen before the helper finishes! The helper’s work might get lost.

graph TD A["Main Thread Starts"] --> B["Spawn Helper Thread"] B --> C["Main: Making soup"] B --> D["Helper: Making salad"] C --> E["Main Ends - Kitchen Closes!"] D -.-> F["Helper might not finish!"] style F fill:#ffcccc

2️⃣ Join Handles: Waiting for Your Helpers

What Is It?

When you create a thread, Rust gives you a ticket called a JoinHandle. This ticket lets you wait for the helper to finish.

The Simple Analogy

It’s like giving your helper chef a walkie-talkie. Before closing the kitchen, you call them: “Are you done yet?” You wait for “Yes!” before leaving.

How to Use Join Handles

use std::thread;

fn main() {
    // spawn returns a JoinHandle (your ticket!)
    let helper = thread::spawn(|| {
        println!("Helper: Making salad...");
        println!("Helper: Salad done!");
    });

    println!("Main: Making soup...");

    // Wait for helper to finish
    helper.join().unwrap();

    println!("Main: Everyone's done! Party time!");
}

What .join() Does

  • Pauses the main thread
  • Waits until the helper thread finishes
  • Only then continues to the next line

Why .unwrap()?

If the helper thread crashes (panics), join() tells you. .unwrap() says “if something went wrong, stop everything!”

graph TD A["Main Thread"] --> B["Spawn Helper"] B --> C["Helper Working..."] A --> D["Main Working..."] D --> E["main calls join"] E --> F{Wait for Helper} C --> F F --> G["Both Done!"] style G fill:#90EE90

3️⃣ Move with Threads: Giving Ownership

The Problem

What if your helper chef needs to use your special recipe book? In Rust, you can’t just share things—you need to say who owns it.

The Simple Analogy

Imagine you have a toy. You can:

  • Show it to a friend (borrowing)
  • Give it to a friend (moving)

With threads, we usually give the toy, so the helper owns it completely.

Why We Need move

use std::thread;

fn main() {
    let recipe = String::from("Secret Sauce Recipe");

    // This WON'T work without 'move':
    // thread::spawn(|| {
    //     println!("Helper reads: {}", recipe);
    // });

    // This WORKS! We give the recipe to the helper
    let helper = thread::spawn(move || {
        println!("Helper reads: {}", recipe);
    });

    helper.join().unwrap();

    // Can't use 'recipe' here anymore!
    // The helper owns it now.
}

What move Does

The move keyword says: “Helper, take ownership of everything you need. It’s yours now!”

Why Is This Safe?

  • No fighting over who owns the recipe
  • The helper can use it as long as needed
  • Rust knows exactly when to clean it up
graph LR A["Main has Recipe"] -->|move| B["Helper owns Recipe"] B --> C["Helper uses Recipe freely"] C --> D["Recipe cleaned up when Helper done"] style A fill:#ffeb3b style B fill:#4caf50

4️⃣ Scoped Threads: Borrowing Without Moving

The Problem with Regular Threads

Sometimes you don’t want to give away your stuff! You just want to let helpers look at it while they work nearby.

The Simple Analogy

You’re doing a group project at school. Everyone sits at the same table and can see the shared textbook. When the project is done, everyone leaves, and you keep your textbook!

How Scoped Threads Work

use std::thread;

fn main() {
    let data = vec![1, 2, 3, 4, 5];

    thread::scope(|s| {
        // Spawn a helper inside the scope
        s.spawn(|| {
            println!("Sum: {}", data.iter().sum::<i32>());
        });

        // Spawn another helper
        s.spawn(|| {
            println!("Count: {}", data.len());
        });

        // Both helpers can SEE 'data'!
    }); // Scope ends here - all helpers must finish

    // 'data' is still ours!
    println!("Data still here: {:?}", data);
}

Why Scoped Threads Are Amazing

  1. No move needed — helpers just borrow
  2. All helpers finish before the scope ends
  3. Your stuff stays yours after the work is done

When to Use Which?

Situation Use This
Helper needs to own the data thread::spawn + move
Multiple helpers share data temporarily thread::scope
Data must outlive the thread thread::spawn + move
Data should stay with main thread::scope
graph TD A["thread::scope starts"] --> B["Helper 1 borrows data"] A --> C["Helper 2 borrows data"] B --> D["Helper 1 finishes"] C --> E["Helper 2 finishes"] D --> F["Scope ends"] E --> F F --> G["Main still has data!"] style G fill:#90EE90

5️⃣ Thread Local Storage: Personal Lockers

What Is It?

Each thread gets its own private locker to store things. Other threads can’t peek inside! It’s like having your own desk drawer at school.

The Simple Analogy

In a kitchen, each chef has their own knife set. Chef A uses their knives. Chef B uses their own. They never mix! Each knife set is “thread-local”.

How to Create Thread Local Storage

use std::cell::RefCell;
use std::thread;

// Create a "personal locker" for each thread
thread_local! {
    static COUNTER: RefCell<u32> = RefCell::new(0);
}

fn main() {
    // Main thread uses its counter
    COUNTER.with(|c| {
        *c.borrow_mut() += 1;
        println!("Main's counter: {}", c.borrow());
    });

    let handle = thread::spawn(|| {
        // This thread has its OWN counter!
        COUNTER.with(|c| {
            *c.borrow_mut() += 100;
            println!("Helper's counter: {}", c.borrow());
        });
    });

    handle.join().unwrap();

    // Check main's counter - still 1!
    COUNTER.with(|c| {
        println!("Main's counter after: {}", c.borrow());
    });
}

Output

Main's counter: 1
Helper's counter: 100
Main's counter after: 1

Key Points

  • thread_local! creates the locker
  • Each thread gets a fresh copy
  • Changes in one thread don’t affect others
  • Use .with() to access your locker

When Is This Useful?

  • Caching data per thread
  • Counters that shouldn’t interfere
  • Any data that should be private to each thread
graph TD subgraph Main Thread A["Counter = 0"] --> B["Counter = 1"] end subgraph Helper Thread C["Counter = 0"] --> D["Counter = 100"] end style A fill:#e3f2fd style B fill:#bbdefb style C fill:#fff3e0 style D fill:#ffe0b2

🎯 Quick Summary

Concept What It Does Think of It As
thread::spawn Creates a new worker Hiring a helper
JoinHandle Ticket to wait for worker Walkie-talkie
move Gives ownership to thread Gifting your toy
thread::scope Lets threads borrow safely Group study table
thread_local! Private storage per thread Personal locker

🚀 You’re Ready!

Now you understand how Rust handles threads safely:

  1. Create helpers with thread::spawn
  2. Wait for them with .join()
  3. Share data with move or scope
  4. Keep private data with thread_local!

Rust’s thread system is like having a perfectly organized kitchen—everyone has their job, their tools, and nobody steps on anyone’s toes!

Go build something amazing with your new thread powers! 🎉

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.