What Are Threads In Rust?

What Are Threads?

Threads are like small, independent “workers” inside your program. Each thread can run its own piece of code at the same time as other threads.

This means your program can do multiple tasks simultaneously, for example:

  • One thread can download a file.
  • Another thread can process data.

Rust’s Thread Model

In many languages, using threads can be risky because if two threads try to change the same data at the same time, it can create bugs or crashes in our app.

Rust solves this problem at compile time using its ownership and borrowing rules. That means Rust checks your code before running and ensures no two threads can change the same data simultaneously.

How To Create Threads in Rust?

We can use the std::thread module and the thread::spawn() function to create threads in Rust.

The thread::spawn() starts a new thread and runs the code you give it (usually inside a closure || { . . . }).

use std::thread;

fn main() {
// Create a new thread
let worker = thread::spawn(|| {
for i in 1..4 {
println!("Worker thread is running task: {}", i);
}
});

// Code running on the main thread
for i in 1..4 {
println!("Main thread is handling: {}", i);
}

// Wait for the worker thread to complete
worker.join().unwrap();
}

Output:

Main thread is handling: 1
Worker thread is running task: 1
Main thread is handling: 2
Worker thread is running task: 2
Main thread is handling: 3
Worker thread is running task: 3

Explanation of this code:

  • thread::spawn(|| { … }) function starts a new thread and runs the code inside the closure.
  • join() ensures the main thread waits for the spawned thread to finish before the program ends.

Passing Data to Threads In Rust

Rust gives us two safe ways to pass data, such as:

  • Transfer ownership using the move keyword.
  • Use references carefully (requires synchronization)

Example: Passing Ownership To a Thread

use std::thread;

fn main() {
let numbers = vec![10, 20, 30];

// 'move' transfers ownership of 'numbers' into the new thread
let handle = thread::spawn(move || {
println!("Inside thread: {:?}", numbers);
});

// Wait for the spawned thread to finish
handle.join().unwrap();
}

Explanation:

  • The move keyword transfers ownership of data to the spawned thread.
  • This avoids conflicts or unexpected behavior when multiple threads access the same data.

Why move is Required?

Without the move keyword, the new thread would try to borrow numbers, but at run time, the main function has already finished, and the numbers could be gone from memory. so Rust forces you to either:

  • Give the data to the thread (ownership transfer), or
  • Use synchronization tools like Arc or Mutex (for shared access advanced topic).

How To Sharing Data Between Threads

If multiple threads need access to the same data, you can use synchronization tools like Arc (atomic reference counter) and Mutex (mutual exclusion lock).

Example: Shared Data with Arc and Mutex

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

fn main() {
// Shared counter wrapped in Arc and Mutex
let count = Arc::new(Mutex::new(0));

let mut thread_handles = vec![];

for _ in 0..5 {
// Clone the Arc pointer (shared ownership)
let shared_count = Arc::clone(&count);

let handle = thread::spawn(move || {
// Lock the Mutex before accessing the data
let mut num = shared_count.lock().unwrap();
*num += 1; // safely update the shared counter
println!("Thread updated counter to: {}", *num);
});

thread_handles.push(handle);
}

// Wait for all threads to finish
for handle in thread_handles {
handle.join().unwrap();
}

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

Explanation:

  • Arc: Allows multiple threads to share ownership of the Mutex.
  • Mutex: Ensures only one thread can access the data at a time, preventing data races.

How To Handle Panics in Threads?

Sometimes a thread might crash unexpectedly while running; this is called a panic. A thread tries to access an invalid index in a list. Or it manually calls panic!() when something goes wrong.

Example: Handling Thread Panics

use std::thread;

fn main() {
let worker = thread::spawn(|| {
println!("Thread started...");

// Simulating an unexpected error
panic!("Oops! Something went wrong in this thread.");
});

// Join waits for the thread to finish and captures the result
match worker.join() {
Ok(_) => println!("Thread completed without errors."),
Err(err) => {
println!("Thread panicked, but the program is still running.");
println!("Panic info: {:?}", err);
}
}

println!("Main program continues even after the panic.");
}

Explanation:

  • thread::spawn(|| { . . . }) creates a new thread. Inside it, we simulate an error with panic!().
  • Use join to handle panics safely. The main thread continues even if a spawned thread fails.

Exercise: “Download Simulator”

Simulate downloading three different files simultaneously using threads. Each thread should “download” a file by printing progress messages.

Instructions:

  1. Create a Rust program that spawns 3 threads, each of which represents downloading a different file:
    • file1.txt
    • file2.txt
    • file3.txt
  2. Inside each thread:
    • Print “Downloading <file>… X%” five times (from 20% to 100%).
    • Add a small delay (e.g., std::thread::sleep) to simulate time passing.
  3. In the main thread, print “All downloads started!” before spawning the threads.
  4. Use .join() to make sure the main program waits for all downloads to finish.

Your Expected Output:

Learn More About Rust Programming

Leave a Comment