Parallel Map with Scoped Threads

Map slices in chunks with std::thread::scope, join worker results in order, and avoid unsafe shared mutable state.

RustData Integrity

Code

snippet
use std::thread;

fn parallel_map<T, U, F>(items: &[T], workers: usize, transform: F) -> Vec<U>
where
    T: Sync,
    U: Send,
    F: Fn(&T) -> U + Sync,
{
    assert!(workers > 0, "workers must be positive");
    let chunk_size = items.len().div_ceil(workers).max(1);
    let mut results = Vec::with_capacity(items.len());

    thread::scope(|scope| {
        let mut handles = Vec::new();
        for chunk in items.chunks(chunk_size) {
            let transform = &transform;
            handles.push(scope.spawn(move || {
                chunk.iter().map(|item| transform(item)).collect::<Vec<U>>()
            }));
        }
        for handle in handles {
            results.extend(handle.join().expect("worker panicked"));
        }
    });

    results
}

fn main() {
    let numbers: Vec<u64> = (1..=10).collect();
    let squares = parallel_map(&numbers, 3, |number| number * number);
    println!("{squares:?}");
    assert_eq!(squares, vec![1, 4, 9, 16, 25, 36, 49, 64, 81, 100]);

    let empty: Vec<u64> = parallel_map(&[] as &[u64], 4, |number| number + 1);
    assert!(empty.is_empty());

    let words = ["a", "bb", "ccc"];
    let lengths = parallel_map(&words, 8, |word| word.len());
    assert_eq!(lengths, vec![1, 2, 3]);
}