Step 7 of 7
rayon: data parallelism made easy
Chunking data, spawning threads, joining them: a lot of ceremony for "do this to every item, on every core". Most Rust teams reach for the rayon crate instead. It keeps a thread pool (about one thread per core, started once and reused) and gives you parallel iterators: change .iter() to .par_iter() and rayon splits the work across the pool.
[dependencies]
rayon = "1"
use rayon::prelude::*;
fn main() {
let orders: Vec<u64> = (1..=1_000).collect();
let revenue: u64 = orders.par_iter().map(|price| price * 2).sum();
let big: Vec<u64> = orders.par_iter().filter(|&&p| p > 995).copied().collect();
println!("revenue: {revenue}");
println!("big orders: {big:?}");
}
revenue: 1001000
big orders: [996, 997, 998, 999, 1000]
use rayon::prelude::*;brings in the traits that add the parallel methods. Forget it and you geterror[E0599]: no method named `par_iter` found for struct `Vec<u64>` in the current scope.par_iter()borrows the items,par_iter_mut()borrows them mutably,into_par_iter()takes ownership (it works on ranges too:(1..=n).into_par_iter()).- Most iterator methods you know have parallel versions:
map,filter,sum,count,min,max,any,all,for_each,collect. On slices there arepar_sort()andpar_sort_unstable(). collect()keeps the original order, even though the items were processed on different threads.sumandcountgive the same answer every run.
The closures can't change captured variables
The same rules as threads apply: closures run on several threads at once, so they may read captured data but not modify it:
use rayon::prelude::*;
fn main() {
let v: Vec<u64> = (1..=10).collect();
let mut count = 0;
v.par_iter().for_each(|_| count += 1);
println!("{count}");
}
error[E0594]: cannot assign to `count`, as it is a captured variable in a `Fn` closure
Use the iterator's own result instead (.count(), .sum()), or an atomic counter (next module).
Is it worth it?
Parallelism has overhead: splitting work and waking threads takes time. For a few hundred cheap items, a plain .iter() is often faster. Use rayon for real work (thousands of items, or expensive work per item), and measure (module 37).
Choosing your tool
| The job | Reach for |
|---|---|
| The same CPU work on many items | rayon (par_iter) |
| A few threads that borrow local data, then finish | thread::scope |
| A long-running background worker | thread::spawn + channels (next module) |
| Thousands of network connections, waiting on I/O | async with Tokio (module 33) |
Your task
Write two functions with rayon's par_iter():
fn total_revenue(orders: &[Order]) -> u64: the sum ofquantity * unit_priceover all orders.fn count_large(orders: &[Order], threshold: u64) -> usize: how many orders havequantity * unit_priceof at leastthreshold.
Challenge 2
Write fn clean_tags(tags: &[&str]) -> Vec<String> with rayon: trim and lowercase every tag in parallel (par_iter), collect them, sort the result with par_sort(), and remove duplicates with dedup().
Challenge 3
Count the primes below 50,000 in parallel. Write fn is_prime(n: u32) -> bool yourself (trial division up to the square root is fine), then use (2..50_000u32).into_par_iter() to count them and to find the largest one. Print:
primes below 50000: 5133
largest: 49999