SyntaxStudy
Sign Up
Rust Threads and the Send and Sync Traits
Rust Beginner 1 min read

Threads and the Send and Sync Traits

Rust's concurrency story begins with OS threads and the promise of "fearless concurrency." The standard library's `std::thread::spawn` creates a new OS thread and takes a `FnOnce + Send + 'static` closure. The `Send` marker trait indicates that a type is safe to transfer to another thread; `Sync` indicates it is safe to share a reference across threads. These traits are automatically implemented for most types and are checked by the compiler. The `move` keyword is almost always needed with `spawn` closures because the spawned thread could outlive the scope where its captured variables live. By moving the data into the closure, Rust ensures the thread owns its data and the borrow checker is satisfied. The `JoinHandle` returned by `spawn` can be awaited with `.join()` to wait for the thread to complete and retrieve its return value (or a panic payload). Data races — where two threads access the same memory concurrently, at least one access is a write, and there is no synchronisation — are completely prevented by the type system. A type that is not `Send` (like `Rc` or raw pointers) cannot be passed to another thread. A type that is not `Sync` (like `Cell`) cannot be shared via a reference. The compiler rejects any code that would introduce a data race.
Example
use std::thread;
use std::time::Duration;

fn main() {
    // Spawn a thread with a move closure
    let data = vec![1, 2, 3, 4, 5];

    let handle = thread::spawn(move || {
        println!("thread sees: {:?}", data);
        let sum: i32 = data.iter().sum();
        sum   // return value from the thread
    });

    // Main thread continues
    println!("main thread is running");

    // Wait for the spawned thread to finish and get its result
    let sum = handle.join().expect("thread panicked");
    println!("thread computed sum = {sum}");

    // Spawn multiple threads
    let handles: Vec<_> = (0..5).map(|i| {
        thread::spawn(move || {
            thread::sleep(Duration::from_millis(50 * i));
            println!("thread {i} done");
            i * i
        })
    }).collect();

    let results: Vec<u64> = handles.into_iter()
        .map(|h| h.join().unwrap())
        .collect();

    println!("squares: {:?}", results);

    // thread::Builder for named threads
    let builder = thread::Builder::new().name("worker".to_string());
    let worker = builder.spawn(|| {
        println!("I am thread '{}'", thread::current().name().unwrap_or("?"));
    }).unwrap();
    worker.join().unwrap();
}