RustArena

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]

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():

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

Previous: When a thread panics