Rust
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Ownership and Borrowing
-
Section 4: Structuring Data
-
Section 5: Collections and Error Handling
-
Section 6: Traits and Generics
-
Section 7: Concurrency
-
Section 8: Building for the Web
-
Section 9: Memory and Performance
-
Section 10: More Standard Library and Ecosystem
-
Section 11: Advanced Rust
-
Section 12: Rust for Systems and WebAssembly
-
Section 13: Tooling and Best Practices
-
Section 14: Data Structures and Algorithms in Rust
-
Section 15: Practical Projects
-
Section 16: Interview Practice
-
Section 17: std::collections In Depth
-
Section 18: std::io and std::fs In Depth
-
Section 19: std::net
-
Section 20: std::option and std::result In Depth
-
Section 21: std::iter In Depth
-
Section 22: std::sync In Depth
-
Section 23: std::string and std::str
-
Section 24: Cargo and Crates.io Ecosystem
-
Section 25: Popular Crates Ecosystem
-
Section 26: Rust Memory Model Deep Dive
-
Section 27: More Practice Exercises
-
Section 28: More Interview Practice
-
Section 29: Async Rust Deep Dive
-
Section 30: Tokio Ecosystem In Depth
-
Section 31: Error Handling Ecosystem Deep Dive
-
Section 32: Serde In Depth
-
Section 33: Web Development with Rust Deep Dive
-
Section 34: Database Access Ecosystem
-
Section 35: Rust for Embedded Systems Deep Dive
-
Section 36: Rust Macros In Depth
-
Section 37: Advanced Trait System
-
Section 38: Unsafe Rust In Depth
-
Section 39: Rust CLI Development
-
Section 40: Testing Ecosystem Deep Dive
-
Section 41: WebAssembly Deep Dive
-
Section 42: Rust Design Patterns
-
Section 43: More Data Structures in Rust
-
Section 44: Final Practice Projects
-
Section 45: Rust Performance Optimization
-
Section 46: Rust Ecosystem Tooling
-
Section 47: More Interview and Review
41: Message Passing with Channels
A few years ago, I was reviewing code for a junior dev who was building a high-throughput data ingestor. They had wrapped their entire central state—a massive hash map—in an Arc<Mutex<T>> and had a dozen threads hammering away at it. On paper, it was thread-safe. In reality, it was a disaster. The threads spent more time fighting over the lock (lock contention) than actually processing data. When I asked why they were sharing the state, they said, "I just need to get the data from the workers to the aggregator."
That's the "aha!" moment. In Rust, we have a saying: Do not communicate by sharing memory; instead, share memory by communicating. Instead of locking a door and waiting in line to enter a room, you just mail the data to the person who needs it. This is where channels come in.
Shipping Data with mpsc
Rust provides a channel implementation called mpsc, which stands for "multi-producer, single-consumer." I like to think of this as a one-way conveyor belt. You can have as many people as you want putting items on the belt (the producers), but only one person at the end picking them up (the consumer).
Let's look at a real-world example. Imagine you're building a log processor where multiple worker threads are scanning files for errors and sending those errors to a single dedicated logger thread that writes to a disk. You don't want every worker thread opening and locking the log file; you just want them to "fire and forget" the message.
use std::sync::mpsc;
use std::thread;
use std::time::Duration;
fn main() {
// tx is the transmitter, rx is the receiver
let (tx, rx) = mpsc::channel();
// We need a separate transmitter for each thread.
// We clone the original transmitter to do this.
for i in 0..3 {
let tx_clone = tx.clone();
thread::spawn(move || {
let msg = format!("Error report from worker {}", i);
tx_clone.send(msg).unwrap();
thread::sleep(Duration::from_millis(100));
});
}
// We drop the original transmitter because we aren't using it
// in the main thread, and the receiver will wait forever if
// at least one transmitter is still alive.
drop(tx);
// The receiver acts as an iterator, blocking until a message arrives
for received in rx {
println!("Logger received: {}", received);
}
}
Notice how tx_clone.send(msg) takes ownership of the string. This is the beauty of Rust's ownership model combined with channels: you aren't just sending a pointer to data that might be mutated elsewhere; you are physically moving the data from one thread's scope to another. Once you send it, the worker thread can't accidentally touch it again.
Blocking, Non-blocking, and Channel Closure
By default, rx.recv() is a blocking call. It puts the thread to sleep until a message arrives, which is incredibly efficient for CPU usage. However, if you need to do other work while waiting, you can use try_recv(), which returns immediately with either a message or an error saying the channel is empty. I usually stick to the blocking iterator approach unless I'm building a game loop or a UI where the thread can't afford to freeze.
One thing that trips people up is how a channel actually "closes." The receiver loop (like the for received in rx loop above) will keep running as long as there is at least one active transmitter. If you clone tx five times and send those clones into five threads, but forget to drop the original tx in your main thread, the loop will never end. The receiver thinks, "Well, the main thread might still send something," and it will hang indefinitely. Always be mindful of who holds the transmitters.
📋 Practical Task
Build a Multi-Sensor Temperature Aggregator
Your task is to create a system that simulates several temperature sensors sending readings to a central hub. The hub should calculate the final average temperature once all sensors have reported in.
- Create a channel that transmits
f64values. - Spawn 5 separate threads. Each thread should simulate a "sensor" by generating a random temperature (or a hardcoded value based on the thread ID) and sending it through the channel.
- The main thread should act as the hub. It must collect all 5 readings using the receiver.
- After all messages are received, the main thread should print the average temperature of all sensors.
- Constraint: Ensure the main thread does not hang by properly handling the transmitter handles.
There are no comments for now.