Rust makes concurrency safe and predictable. You can create threads using std::thread, and communicate between them using channels from std::sync::mpsc. This lets you write multi-threaded programs without fear of data races or unsafe behavior.
The first time I used threads in Rust, I was surprised by how easy it was. Coming from JavaScript’s async world, the idea of real OS threads felt heavy. But Rust made it simple, with thread::spawn, safe sharing, and strict compile-time checks that catch mistakes early.
In this article, we will cover how to create threads, send messages between them, and avoid common pitfalls, all in a beginner-friendly way.
Creating a New Thread
Use thread::spawn to run a function on a new thread:
use std::thread;
fn main() {
let handle = thread::spawn(|| {
println!("Running in a new thread!");
});
handle.join().unwrap();
}
- The closure runs in a separate thread.
- join() waits for the thread to finish.
If you skip join(), the program may exit before the thread runs.
Passing Data to Threads
If you want to send data into the thread:
fn main() {
let msg = String::from("hello from main");
let handle = thread::spawn(move || {
println!("{}", msg);
});
handle.join().unwrap();
}
You must use move to transfer ownership into the thread.
Spawning Multiple Threads
for i in 0..5 {
thread::spawn(move || {
println!("Thread number {}", i);
});
}
Each thread runs its own closure. You can create hundreds of threads this way.
Communicating Between Threads
Rust uses channels for message passing. A channel has:
- Sender (tx)
- Receiver (rx)
use std::sync::mpsc;
use std::thread;
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
tx.send("message from thread").unwrap();
});
let received = rx.recv().unwrap();
println!("Main got: {}", received);
}
This is useful when one thread does work and reports results back.
Sending Multiple Messages
Use a loop with send and recv:
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
for i in 1..4 {
tx.send(i).unwrap();
}
});
for received in rx {
println!("Got: {}", received);
}
The loop on rx ends when the sender is dropped.
Cloning the Sender
You can send from multiple threads by cloning the sender:
let (tx, rx) = mpsc::channel();
let tx2 = tx.clone();
thread::spawn(move || {
tx.send("from first").unwrap();
});
thread::spawn(move || {
tx2.send("from second").unwrap();
});
This lets you merge messages from many sources into one receiver.
Thread Safety
Rust prevents:
- Data races
- Use-after-free
- Race conditions due to shared mutation
That is why it enforces ownership, borrowing, and thread-safe traits like Send and Sync.
You cannot share most values between threads without wrapping them in Arc, Mutex, or similar. These are covered in detail in the advanced concurrency section.
Summary
Rust gives you low-level control over threads and makes concurrency safe by default. You can spawn threads, pass data using channels, and avoid unsafe behavior. The compiler helps you build concurrent systems with confidence, without crashes or surprises.
Rust Intermediate Concepts
- Generic Types in Functions and Structs
- Implementing Traits for Custom Behavior in Rust
- Trait Bounds and Constraints in Rust
- Lifetimes in Rust
- Using Closures (Anonymous Functions) in Rust
- The Iterator Trait and .next() in Rust
- Higher Order Iterator Methods: map, filter, and fold in Rust
- Using impl Trait for Simplicity in Rust
- Advanced Collections: HashSet and BTreeMap in Rust
- Custom Error Types and the Error Trait in Rust
- Option and Result Combinators in Rust
- Writing Unit Tests in Rust
- Integration Testing in Rust
- Logging in Rust with the log Crate
- Cargo Tips and Tricks for Rust Projects
- CLI Argument Parsing with Clap in Rust
- File I/O and File System Operations in Rust
- Running External Commands in Rust
- Make HTTP Requests in Rust with Reqwest
- JSON Serialization and Deserialization in Rust with Serde
- Building a Weather CLI in Rust
- Date and Time Handling in Rust with Chrono
- Using Regular Expressions in Rust with the regex Crate
- Memory Management in Rust
- Understanding Borrow Checker Errors in Rust
- Interacting with Databases in Rust
- Building a Todo List CLI in Rust with SQLite
- Concurrency in Rust