-
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
47: Practice Exercise: Building a Concurrent Word Counter
How do I share the source text without copying it for every thread?
This is where a lot of people trip up. If you try to pass a reference &str into a thread, the compiler will scream at you because it can't prove the text will live long enough. You could clone the string for every thread, but if you're processing a 100MB log file, that's a massive waste of RAM.
The move here is Arc (Atomic Reference Counted). Wrap your text in an Arc, and then clone the handle, not the data. Every thread gets its own pointer to the same heap allocation. I usually pair this with indices to tell each thread which "slice" of the text to look at.
use std::sync::Arc;
use std::thread;
let text = Arc::new("The quick brown fox jumps over the lazy dog".to_string());
let mut handles = vec![];
for i in 0..4 {
let text_share = Arc::clone(&text);
let handle = thread::spawn(move || {
// Each thread accesses the same underlying string
println!("Thread {} sees: {}", i, text_share);
});
handles.push(handle);
}
Should I use a Mutex for the map or just send results back via channels?
You can absolutely wrap a HashMap in a Mutex and have every thread lock it to increment a count. But honestly? Don't do that for this exercise. Mutex contention is a performance killer. If four threads are constantly fighting for the lock just to increment a single word count, you've basically turned your multi-threaded program back into a single-threaded one, just with more overhead.
The cleaner, "Rust-y" way is to let each thread maintain its own local HashMap. When the thread finishes its chunk of text, it sends the entire local map back to the main thread via a channel (std::sync::mpsc). Then, the main thread merges those maps into one final result. It's way faster because the threads never have to wait on each other.
How do I handle the "remainder" if the text doesn't divide evenly?
If you have 1,003 words and 4 threads, you can't just give everyone 250 words. You'll miss three. I've seen people try to over-complicate this with complex math, but the simplest approach is to calculate the chunk size and let the final thread just take "everything that's left."
If you're working with indices, just make sure your last range ends at text.len() rather than start + chunk_size. Just a heads-up: be careful with UTF-8 boundaries. If you slice a string in the middle of a multi-byte character, Rust will panic. To avoid this, I recommend splitting the text into a Vec&str of words first, then chunking that vector. It's a bit more memory, but it saves you from a debugging nightmare with byte offsets.
📋 Practical Task
Exercise: Building a Multi-Threaded Word Frequency Analyzer
Your goal is to build a program that counts the frequency of words in a large string concurrently. Instead of a single-threaded loop, you will implement a "map-reduce" style approach.
- The Input: A long string of text (at least 20-30 words).
- The Logic:
- Split the text into a
Vec&strof words. - Divide that vector into 4 chunks.
- Spawn 4 threads. Each thread should calculate the word frequencies for its chunk and store them in a local
HashMap<String, usize>. - Use an
mpsc::channelto send these local maps back to the main thread. - In the main thread, receive all 4 maps and merge them into one final
HashMap.
- Split the text into a
- The Output: Print the final word counts to the console.
Challenge: Ensure that your program handles cases where the number of words isn't perfectly divisible by 4 without crashing or skipping words.
There are no comments for now.