# Threads, Send, and Sync

Move or borrow data across threads, then handle completion and failure.

Canonical: https://rust.robertdevore.com/course/16-threads/
Author: Robert DeVore
Technical baseline: Rust 1.98.1, edition 2024; verified 2026-09-06.

## Pass data to a thread

A thread can outlive the function that started it. Ordinary `thread::spawn` therefore requires a closure and result satisfying `Send + 'static`. Owned data can satisfy that bound without living forever. A borrowed local cannot simply be sent into a potentially longer-lived thread.

Scoped threads give a different guarantee: they finish before the scope returns. This permits borrowing data that remains valid for that scope.


### Run the example

```sh
cargo run --locked --example 16_threads
```

```rust
fn main() {
    let rows = ["INFO a", "WARN b", "WARN c", "ERROR d"];
    let total = std::thread::scope(|scope| {
        let left = scope.spawn(|| rows[..2].iter().filter(|r| r.starts_with("WARN ")).count());
        let right = scope.spawn(|| rows[2..].iter().filter(|r| r.starts_with("WARN ")).count());
        left.join().unwrap() + right.join().unwrap()
    });
    assert_eq!(total, 2);
}
```

[View the tested source](https://github.com/robertdevore/rust.robertdevore.com/blob/main/examples/16_threads.rs)


Two threads read disjoint ranges of the same immutable array. They return counts, and the parent combines them. No lock is needed because there is no shared mutation. `join` also reports a worker panic. The example uses `unwrap` so a panic fails the example. In an application, decide how to report that failure.

## What Send and Sync mean

`Send` means a value can safely be transferred to another thread. `Sync` means a shared reference to the type can safely be transferred between threads. More precisely, `T: Sync` relates to `&T: Send`. These are unsafe traits to implement manually; normally let their automatic derivation from fields describe your type.

`Rc` is not `Send` because its reference count is not synchronized. `Arc` synchronizes ownership bookkeeping. Sharing mutable inner data still needs an appropriate mechanism and trait bounds. Using `Arc` alone does not make access to the data safe.

## Compiler drill


**Intentionally fails on Rust 1.98.1.** Run `rustc --edition=2024 drills/send.rs` from the repository.

```rust
fn main() {
    let value = std::rc::Rc::new(1);
    std::thread::spawn(move || println!("{value}"));
}
```

<details><summary>Actual compiler diagnostic · Rust 1.98.1</summary>

```text
error[E0277]: `Rc<i32>` cannot be sent between threads safely
 --> drills/send.rs:3:24
  |
3 |     std::thread::spawn(move || println!("{value}"));
  |     ------------------ -------^^^^^^^^^^^^^^^^^^^^
  |     |                  |
  |     |                  `Rc<i32>` cannot be sent between threads safely
  |     |                  within this `{closure@drills/send.rs:3:24: 3:31}`
  |     required by a bound introduced by this call
  |
  = help: within `{closure@drills/send.rs:3:24: 3:31}`, the trait `Send` is not implemented for `Rc<i32>`
note: required because it's used within this closure
 --> drills/send.rs:3:24
  |
3 |     std::thread::spawn(move || println!("{value}"));
  |                        ^^^^^^^
note: required by a bound in `spawn`
 --> /rustc/48a229ceaefd4985c50990b14116b6d856af0985/library/std/src/thread/functions.rs:125:0

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0277`.
```

</details>


The compiler traces the requirement from the thread closure to the captured `Rc`. For immutable shared data, `Arc` may be the right correction. If the parent no longer needs the data, moving an ordinary owned value may be simpler. Do not add a mutex when no thread mutates anything.

## Correctness extends beyond data races

Rust's type system and sound libraries rule out many data races in safe code, but not deadlocks, starvation, lost application updates, or wrong ordering of business events. The OS or runtime schedules threads. Rust does not promise that a new thread runs immediately or that threads run in creation order.

Parallel work can also be slower. Thread creation, communication, cache effects, and small inputs can dominate useful computation. Our tiny fixture demonstrates ownership, not a performance win. Benchmark a realistic workload before choosing worker counts.

## Exercise

Repair the drill twice: once by moving a plain integer owner, and once by using `Arc` for immutable sharing. Then explain why the scoped example requires neither an atomic reference count nor a mutex.

<details><summary>Solution and acceptance check</summary>

The move-only version captures an owned value. The shared version clones an `Arc` handle before spawning and joins the thread. Scoped borrows are valid because the scope waits for workers, and shared immutable access does not require a lock. Each version should terminate and produce the same value without ignoring a join result.

</details>

Sources: [`thread::scope`](https://doc.rust-lang.org/std/thread/fn.scope.html), [`Send`](https://doc.rust-lang.org/std/marker/trait.Send.html), and [`Sync`](https://doc.rust-lang.org/std/marker/trait.Sync.html).

