Send and Sync
Concurrency · Lesson 44
Two questions decide whether a type may be used from more than one thread. Send: may this value be moved to another thread? Sync: may several threads hold a &T to it at once? The compiler answers both from the types and refuses the program when the answer is no. Together with the borrow rules, that check is how safe Rust has no data races, and it is why Arc<Mutex<T>> compiles across threads where Rc<RefCell<T>> does not.
Coming from GoGo compiles a program that shares a plain variable between goroutines, and the race detector (go run -race) reports unsynchronized accesses it sees when the program runs. Rust asks at compile time instead, from the types: Send ("this value may move to another thread") and Sync ("a &T may be used by several threads"). There is no flag to switch it on.
Coming from KotlinWhether a class is safe to use from several threads is something you read in its documentation (the Java ArrayList page says it is not synchronized) or in an annotation such as @ThreadSafe, and javac checks neither unless you add a checker. Send and Sync put that fact in the type: the compiler works it out from the fields and refuses a call that hands the type to a thread it is not safe on.
Coming from PythonIn the standard CPython build only one thread runs Python code at a time (the GIL), and that does not make "read the variable, then write it" atomic: the Python FAQ lists i = i+1 as not atomic, and free-threaded builds have no such lock at all. Rust has no interpreter lock, and instead the compiler checks Send and Sync on every value that crosses to a thread.
Coming from TypeScriptWorker threads do not share objects by default: workerData and messages are copies (unless you transfer an ArrayBuffer), and sharing is opt-in with SharedArrayBuffer and Atomics. Rust threads live in one address space and can share memory, so it needs a rule for which types may be shared: Send and Sync, checked at compile time.
Coming from JavaThread safety is documentation or annotation: ArrayList says it is not synchronized and ConcurrentHashMap says it is, and javac checks neither (a checker such as Error Prone's @Immutable is opt-in). A Java data race has a limited set of allowed outcomes under the memory model, which is not the same as a correct result. In Rust safe code cannot have one, because Send and Sync are checked on every value a thread receives.
Coming from C#The List<T> page says instance members are not guaranteed to be thread safe, and the compiler does not check that you respected it. Send and Sync are the same facts as traits the compiler works out and enforces: Arc<Mutex<List>> compiles across threads where a type holding an Rc does not.
Coming from Cpthread_create takes a void *, so any pointer can go to any thread and nothing in the type says whether sharing it is safe. A data race is undefined behavior. Send and Sync are that missing information, written in the type and checked by the compiler.
Coming from C++std::thread accepts any callable, including a lambda that captures a local by reference, and a data race is undefined behavior (cppreference). Rust's Send and Sync are checked on everything a thread receives, and a type with a non-atomic reference count, like Rc, is not allowed to cross.
The quick comparison
use std::thread;
fn main() { let words = vec![String::from("alpha"), String::from("beta")];
// Send: the Vec may move to another thread, and come back as its result let words = thread::spawn(move || words).join().unwrap(); println!("moved there and back: {words:?}");
// Sync: two threads may read the same Vec at once, through `&` let (a, b) = thread::scope(|s| { let first = s.spawn(|| words[0].len()); let second = s.spawn(|| words[1].len()); (first.join().unwrap(), second.join().unwrap()) }); println!("lengths read in parallel: {a} and {b}");}moved there and back: ["alpha", "beta"] lengths read in parallel: 5 and 4
package main
import ( "fmt" "sync")
func main() { words := []string{"alpha", "beta"}
// hand the slice to another goroutine and get it back over a channel back := make(chan []string) go func() { back <- words }() words = <-back fmt.Printf("moved there and back: [%q, %q]\n", words[0], words[1])
// two goroutines read the same slice at once; nothing checks that this is safe lens := make([]int, 2) var wg sync.WaitGroup for i := range lens { wg.Add(1) go func(i int) { defer wg.Done() lens[i] = len(words[i]) }(i) } wg.Wait() fmt.Printf("lengths read in parallel: %d and %d\n", lens[0], lens[1])}moved there and back: ["alpha", "beta"] lengths read in parallel: 5 and 4
import java.util.concurrent.Executors
fun main() { val words = listOf("alpha", "beta") val pool = Executors.newFixedThreadPool(2)
// hand the list to a pool thread and get it back as the result val back = pool.submit<List<String>> { words }.get() println("moved there and back: " + back.joinToString(", ", "[", "]") { "\"$it\"" })
// two threads read the same list at once; nothing checks that this is safe val a = pool.submit<Int> { words[0].length } val b = pool.submit<Int> { words[1].length } println("lengths read in parallel: ${a.get()} and ${b.get()}")
pool.shutdown()}moved there and back: ["alpha", "beta"] lengths read in parallel: 5 and 4
import jsonfrom concurrent.futures import ThreadPoolExecutor
words = ["alpha", "beta"]
with ThreadPoolExecutor() as pool: # hand the list to a pool thread and get it back as the result back = pool.submit(lambda: words).result() print("moved there and back:", json.dumps(back))
# two threads read the same list at once; nothing checks that this is safe a = pool.submit(lambda: len(words[0])) b = pool.submit(lambda: len(words[1])) print(f"lengths read in parallel: {a.result()} and {b.result()}")moved there and back: ["alpha", "beta"] lengths read in parallel: 5 and 4
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
// Run this same file in a worker. `data` is copied to it, and the answer is copied back.function inWorker<T>(data: unknown): Promise<T> { return new Promise((resolve) => { new Worker(__filename, { workerData: data }).on("message", resolve); });}
if (isMainThread) { (async () => { const words = ["alpha", "beta"];
const back = await inWorker<string[]>({ kind: "echo", words }); console.log(`moved there and back: [${back.map((w) => JSON.stringify(w)).join(", ")}]`);
// two workers, each with its own copy of the list const [a, b] = await Promise.all([ inWorker<number>({ kind: "len", words, i: 0 }), inWorker<number>({ kind: "len", words, i: 1 }), ]); console.log(`lengths read in parallel: ${a} and ${b}`); })();} else { const { kind, words, i } = workerData; parentPort!.postMessage(kind === "echo" ? words : words[i].length);}moved there and back: ["alpha", "beta"] lengths read in parallel: 5 and 4
import java.util.List;import java.util.concurrent.Executors;import java.util.stream.Collectors;
public class Main { public static void main(String[] args) throws Exception { var words = List.of("alpha", "beta");
try (var pool = Executors.newFixedThreadPool(2)) { // hand the list to a pool thread and get it back as the result var back = pool.submit(() -> words).get(); var quoted = back.stream().map(w -> "\"" + w + "\"").collect(Collectors.joining(", ", "[", "]")); System.out.println("moved there and back: " + quoted);
// two threads read the same list at once; nothing checks that this is safe var a = pool.submit(() -> words.get(0).length()); var b = pool.submit(() -> words.get(1).length()); System.out.println("lengths read in parallel: " + a.get() + " and " + b.get()); } }}moved there and back: ["alpha", "beta"] lengths read in parallel: 5 and 4
string[] words = ["alpha", "beta"];
// Task.Run runs the work on a pool thread; the result comes back through the Taskvar back = await Task.Run(() => words);Console.WriteLine("moved there and back: [" + string.Join(", ", back.Select(w => $"\"{w}\"")) + "]");
// two pool threads read the same array at once; nothing checks that this is safevar a = Task.Run(() => words[0].Length);var b = Task.Run(() => words[1].Length);Console.WriteLine($"lengths read in parallel: {await a} and {await b}");moved there and back: ["alpha", "beta"] lengths read in parallel: 5 and 4
#include <pthread.h>#include <stdio.h>#include <string.h>
static const char *words[2] = {"alpha", "beta"};
void *echo(void *arg) { return arg; // a thread hands back a pointer}
void *length(void *arg) { return (void *)strlen(*(const char **)arg);}
int main(void) { pthread_t t; void *back; pthread_create(&t, NULL, echo, words); pthread_join(t, &back); // wait, and collect the result const char **w = back; printf("moved there and back: [\"%s\", \"%s\"]\n", w[0], w[1]);
// two threads read the same array at once; nothing checks that this is safe pthread_t a, b; void *la, *lb; pthread_create(&a, NULL, length, &words[0]); pthread_create(&b, NULL, length, &words[1]); pthread_join(a, &la); pthread_join(b, &lb); printf("lengths read in parallel: %zu and %zu\n", (size_t)la, (size_t)lb); return 0;}moved there and back: ["alpha", "beta"] lengths read in parallel: 5 and 4
#include <future>#include <iostream>#include <string>#include <vector>
int main() { std::vector<std::string> words{"alpha", "beta"};
// std::async runs the lambda on a new thread and hands back its result later auto back = std::async(std::launch::async, [&words] { return words; }).get(); std::cout << "moved there and back: [\"" << back[0] << "\", \"" << back[1] << "\"]\n";
// two threads read the same vector at once; nothing checks that this is safe auto a = std::async(std::launch::async, [&words] { return words[0].size(); }); auto b = std::async(std::launch::async, [&words] { return words[1].size(); }); std::cout << "lengths read in parallel: " << a.get() << " and " << b.get() << "\n";}moved there and back: ["alpha", "beta"] lengths read in parallel: 5 and 4
What's different
Sendmeans a value may be moved to another thread.Syncmeans a&Tmay be shared between threads; the standard library defines it as "TisSyncif and only if&TisSend". Both are marker traits with no methods.- The compiler implements them for you, field by field: a struct is
Sendwhen every field is. OneRcanywhere inside makes the whole struct notSend, and the error names the chain from the thread call down to theRc. thread::spawnneeds the closure and the thread's result to beSend(and'static).Arc<T>isSendonly whenTisSendandSync:RefCell<i32>is notSync,Mutex<i32>is, which is why the shared-counter pattern isArc<Mutex<T>>.- Safe Rust has no data races, and it still has deadlocks and logic races. Two correct
lock()calls with a gap between a check and the change compile, run, and give a wrong answer.
A struct that holds an Rc, handed to a thread
Looks fine: in Go, Kotlin, Python, TypeScript, Java, C#, C and C++ it runs fine. Rust won't compile it.
Fine in Go. Rust can't prove it's safe.Fine in Kotlin. Rust can't prove it's safe.Fine in Python. Rust can't prove it's safe.Fine in TypeScript. Rust can't prove it's safe.Fine in Java. Rust can't prove it's safe.Fine in C#. Rust can't prove it's safe.Fine in C. Rust can't prove it's safe.Fine in C++. Rust can't prove it's safe.use std::cell::RefCell;use std::rc::Rc;use std::thread;
struct Job { name: String, log: Rc<RefCell<Vec<String>>>,}
fn run(job: Job) { job.log.borrow_mut().push(job.name);}
fn main() { let log = Rc::new(RefCell::new(Vec::new())); let job = Job { name: String::from("resize"), log: Rc::clone(&log), }; thread::spawn(move || run(job)).join().unwrap(); println!("log: {:?}", log.borrow());}package main
import "fmt"
type Job struct { name string log *[]string // shared with main}
func run(job Job) { *job.log = append(*job.log, job.name)}
func main() { log := []string{} job := Job{"resize", &log} done := make(chan struct{}) go func() { run(job) close(done) }() <-done fmt.Printf("log: %q\n", log)}log: ["resize"]
import kotlin.concurrent.thread
class Job(val name: String, val log: MutableList<String>)
fun run(job: Job) { job.log.add(job.name)}
fun main() { val log = mutableListOf<String>() val job = Job("resize", log) val t = thread { run(job) } t.join() println("log: " + log.joinToString(", ", "[", "]") { "\"$it\"" })}log: ["resize"]
import jsonimport threading
class Job: def __init__(self, name, log): self.name = name self.log = log
def run(job): job.log.append(job.name)
log = []job = Job("resize", log)t = threading.Thread(target=run, args=(job,))t.start()t.join()print("log:", json.dumps(log))log: ["resize"]
import { Worker, isMainThread, parentPort, workerData } from "node:worker_threads";
if (isMainThread) { const log: string[] = []; const job = { name: "resize", log }; // the worker receives a copy of `job`, not the same object const worker = new Worker(__filename, { workerData: job }); worker.on("exit", () => { console.log(`log: [${log.map((s) => JSON.stringify(s)).join(", ")}]`); });} else { workerData.log.push(workerData.name); // changes the worker's copy only parentPort!.postMessage("done");}log: []
import java.util.ArrayList;import java.util.List;
public class Main { record Job(String name, List<String> log) {}
static void run(Job job) { job.log().add(job.name()); }
public static void main(String[] args) throws Exception { List<String> log = new ArrayList<>(); Job job = new Job("resize", log); Thread t = new Thread(() -> run(job)); t.start(); t.join(); System.out.println("log: [\"" + String.join("\", \"", log) + "\"]"); }}log: ["resize"]
var log = new List<string>();var job = new Job("resize", log);
var t = new Thread(() => Run(job));t.Start();t.Join();Console.WriteLine("log: [" + string.Join(", ", log.Select(s => $"\"{s}\"")) + "]");
static void Run(Job job) => job.Log.Add(job.Name);
record Job(string Name, List<string> Log);log: ["resize"]
#include <pthread.h>#include <stdio.h>#include <string.h>
struct Job { const char *name; char *log; // a buffer shared with main};
void *run(void *arg) { struct Job *job = arg; strcat(job->log, job->name); return NULL;}
int main(void) { char log[64] = ""; struct Job job = {"resize", log}; pthread_t t; pthread_create(&t, NULL, run, &job); pthread_join(t, NULL); printf("log: [\"%s\"]\n", log); return 0;}log: ["resize"]
#include <iostream>#include <memory>#include <string>#include <thread>#include <vector>
struct Job { std::string name; std::shared_ptr<std::vector<std::string>> log; // copying the pointer is thread-safe; the vector is not locked};
void run(Job job) { job.log->push_back(job.name);}
int main() { auto log = std::make_shared<std::vector<std::string>>(); Job job{"resize", log}; std::thread t(run, job); t.join(); std::cout << "log: [\"" << (*log)[0] << "\"]\n";}log: ["resize"]
What does the compiler say? Guess first, then open.
error[E0277]: `Rc<RefCell<Vec<String>>>` cannot be sent between threads safely --> src/main.rs:20:19 | 20 | thread::spawn(move || run(job)).join().unwrap(); | ------------- -------^^^^^^^^^ | | | | | `Rc<RefCell<Vec<String>>>` cannot be sent between threads safely | | within this `{closure@src/main.rs:20:19: 20:26}` | required by a bound introduced by this call | = help: within `{closure@src/main.rs:20:19: 20:26}`, the trait `Send` is not implemented for `Rc<RefCell<Vec<String>>>` note: required because it appears within the type `Job` --> src/main.rs:5:8 | 5 | struct Job { | ^^^ note: required because it's used within this closure --> src/main.rs:20:19 | 20 | thread::spawn(move || run(job)).join().unwrap(); | ^^^^^^^ ... (notes pointing into the standard library trimmed)
Go runs this and prints log: ["resize"]. The Job holds a pointer to the slice and the compiler does not ask which goroutines may use it. go run -race can report a race when two goroutines touch it without synchronization.
Kotlin runs this and prints the log. A MutableList is a plain ArrayList, which is not synchronized, and nothing stops you from handing it to a thread. It is correct here because of join().
Python prints log: ["resize"]. Any object can be given to a thread, and the list is correct here because the main thread waits in join().
Prints log: []. A worker does not get the same object: workerData is a copy, so the worker changes its own list and the main thread's list stays empty. Nothing is shared unless you ask for a SharedArrayBuffer.
Java runs this and prints the log. The ArrayList is documented as not synchronized, and the compiler does not check how you share it. It is correct here because join() orders the worker's add before the main thread's read.
C# runs this and prints the log. List<T> is not guaranteed to be thread safe, and nothing enforces how you share it. It is correct here because Join() waits for the thread to finish.
C runs this and prints the log. The worker gets a pointer to a shared buffer, and nothing in the types says whether that is safe. It is correct here because of pthread_join.
C++ runs this and prints the log. A shared_ptr is safe to copy from several threads (its count is atomic in practice), but the vector inside is not locked. It is correct here because join() orders the worker's push before the main thread's read.
thread::spawn needs everything the closure takes to be Send. The closure takes job, a Job holds an Rc, and Rc is not Send, so Job is not Send and the closure is not either. The error names that chain.
The standard library says an Rc that two threads clone at the same time can corrupt its count, because the count is not atomic. Your program is fine: the main thread is waiting in join, so only one thread touches the count at a time. The compiler judges the type, not that argument, and the same shape without the waiting would be a data race. Swap the Rc<RefCell<..>> for Arc<Mutex<..>>, as fixed.rs does.
The fix
Compiles and runs.
use std::sync::{Arc, Mutex};use std::thread;
struct Job { name: String, log: Arc<Mutex<Vec<String>>>,}
fn run(job: Job) { job.log.lock().unwrap().push(job.name);}
fn main() { let log = Arc::new(Mutex::new(Vec::new())); let job = Job { name: String::from("resize"), log: Arc::clone(&log), }; thread::spawn(move || run(job)).join().unwrap(); println!("log: {:?}", log.lock().unwrap());}log: ["resize"]
The toolkit
Outputs come from Linux on 64-bit ARM with Rust 1.99.1, debug build. Only the Python lost-update totals change from run to run, and the text says so.
Two questions about a type
Threads share one address space, so a type that is fine on one thread can cause a data race on two: both use the same memory, at least one writes, nothing orders them. Safe Rust rules that out in two halves. One is the borrow rules: a &mut is exclusive (Mutable borrows). The other is two marker traits, traits with no methods that state a fact about a type:
Send: a value of this type may be moved to another thread.Sync: a&Tmay be used by several threads at once. The standard library’s definition is “TisSyncif and only if&TisSend”.
Nearly everything has both; the exceptions change data without telling anyone, or count owners without locking. If you share data by never changing it, Sync is the compiler-checked version: nothing changes a Vec<String> through a &, so threads can read an Arc<Vec<String>> with no lock.
A function asks for the trait in a bound, as thread::spawn does (Reading signatures). To ask the compiler yourself, state a bound in a function that does nothing:
use std::sync::{Arc, Mutex};
fn assert_send<T: Send>() {}fn assert_sync<T: Sync>() {}
struct Job { id: u32, name: String, tags: Vec<String>,}
fn main() { // each call compiles only if the bound holds; nothing happens when it runs assert_send::<i32>(); assert_send::<Vec<String>>(); assert_sync::<String>(); assert_send::<Arc<Mutex<Vec<String>>>>(); assert_sync::<Arc<Mutex<Vec<String>>>>();
// no `impl Send for Job` anywhere: the compiler derives it from the fields assert_send::<Job>(); assert_sync::<Job>();
let job = Job { id: 7, name: String::from("resize"), tags: vec![String::from("image")], }; println!("{} {} {:?}: Send and Sync", job.id, job.name, job.tags);}7 resize ["image"]: Send and Sync
Each call compiles only if the bound holds. Job has no impl Send: the compiler worked it out from its fields. Two types that fail:
use std::rc::Rc;
fn assert_send<T: Send>() {}
fn main() { assert_send::<Rc<i32>>();}error[E0277]: `Rc<i32>` cannot be sent between threads safely --> src/main.rs:6:19 | 6 | assert_send::<Rc<i32>>(); | ^^^^^^^ `Rc<i32>` cannot be sent between threads safely | = help: the trait `Send` is not implemented for `Rc<i32>` note: required by a bound in `assert_send` --> src/main.rs:3:19 | 3 | fn assert_send<T: Send>() {} | ^^^^ required by this bound in `assert_send`
An Rc updates its count without locking, so the standard library says two threads cloning it could corrupt the count. A Cell may move to another thread but not be shared, because Cell::set takes &self:
use std::cell::Cell;
fn assert_send<T: Send>() {}fn assert_sync<T: Sync>() {}
fn main() { assert_send::<Cell<i32>>(); // fine: a Cell may move to another thread assert_sync::<Cell<i32>>(); // two threads may not share one}error[E0277]: `Cell<i32>` cannot be shared between threads safely --> src/main.rs:8:19 | 8 | assert_sync::<Cell<i32>>(); // two threads may not share one | ^^^^^^^^^ `Cell<i32>` cannot be shared between threads safely | = help: the trait `Sync` is not implemented for `Cell<i32>` = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicI32` instead note: required by a bound in `assert_sync` --> src/main.rs:4:19 | 4 | fn assert_sync<T: Sync>() {} | ^^^^ required by this bound in `assert_sync`
The types you meet
Which of the two does a thread call ask for? Look at what happens to the value. Error messages use the same words: “cannot be sent” is Send, “cannot be shared” is Sync.
- It is moved to another thread:
thread::spawn(move || ...), a channel’ssend, a thread’s return value. That needsSend. - Several threads look at it through a reference: a
thread::scopeborrow, astatic, theTinside anArc<T>. That needsSync. (TheArcitself is moved too, so it needsSend.)
| Type | Send |
Sync |
Because |
|---|---|---|---|
i32, String, Vec<String> |
yes | yes | plain owned data; a Vec<T> is as good as its T |
Rc<i32> |
no | no | the count is not atomic |
Arc<i32> |
yes | yes | the count is atomic; Arc<T> needs T to be both Send and Sync |
Cell<i32>, RefCell<i32> |
yes | no | change through &self with no synchronization |
Mutex<i32> |
yes | yes | access is one thread at a time; needs T: Send |
Mutex<Rc<i32>> |
no | no | T: Send fails: clones of the Rc escape the lock |
MutexGuard<'_, i32> |
no | yes | pthreads release a lock on the thread that took it (docs) |
*const i32, *mut i32 |
no | no | a lint (Going deeper) |
&T |
if T: Sync |
if T: Sync |
sending a &T is sharing the T |
&mut T |
if T: Send |
if T: Sync |
sending a &mut T hands the T over |
&mut Rc<i32> |
no | no | the T is not Send |
Arc<RefCell<i32>> |
no | no | RefCell is not Sync, so the Arc is not Send |
Arc<Mutex<i32>> |
yes | yes | the pattern for shared, changeable data |
RwLock<Cell<i32>> |
yes | no | many readers hold &T at once, so T must be Sync too |
Receiver<i32> |
yes | no | one consumer (Channels) |
check-table.rs asks the compiler about every row, both columns, and fails if one differs. It also checks that &T is Send exactly when T is Sync, for nine types.
They come from the fields
Send and Sync are auto traits: the compiler gives a struct, tuple or closure the trait when all its fields have it, with no impl. So one bad field spoils every struct, tuple or closure that contains it. Types with their own rules, such as Mutex, Arc and &T, decide differently. Here Job holds a Log, which holds an Rc (the gotcha’s Job, one level deeper):
use std::cell::RefCell;use std::rc::Rc;use std::thread;
struct Log { lines: Rc<RefCell<Vec<String>>>,}
struct Job { name: String, log: Log,}
fn run(job: Job) { job.log.lines.borrow_mut().push(job.name);}
fn main() { let job = Job { name: String::from("resize"), log: Log { lines: Rc::new(RefCell::new(Vec::new())), }, }; thread::spawn(move || run(job)).join().unwrap();}error[E0277]: `Rc<RefCell<Vec<String>>>` cannot be sent between threads safely --> src/main.rs:25:19 | 25 | thread::spawn(move || run(job)).join().unwrap(); | ------------- -------^^^^^^^^^ | | | | | `Rc<RefCell<Vec<String>>>` cannot be sent between threads safely | | within this `{closure@src/main.rs:25:19: 25:26}` | required by a bound introduced by this call | = help: within `{closure@src/main.rs:25:19: 25:26}`, the trait `Send` is not implemented for `Rc<RefCell<Vec<String>>>` note: required because it appears within the type `Log` --> src/main.rs:5:8 | 5 | struct Log { | ^^^ note: required because it appears within the type `Job` --> src/main.rs:9:8 | 9 | struct Job { | ^^^ note: required because it's used within this closure --> src/main.rs:25:19 | 25 | thread::spawn(move || run(job)).join().unwrap(); | ^^^^^^^ ... (notes pointing into the standard library trimmed)
Read the message from the top. The first line names the field type, Rc<RefCell<Vec<String>>>, not Job. “Required because it appears within the type Log” and then Job walk up the containers, and “used within this closure” says the closure took the Job. A last note, trimmed from the display, points at the bound in spawn’s signature. Fix the field and the chain disappears:
use std::sync::{Arc, Mutex};use std::thread;
struct Log { lines: Arc<Mutex<Vec<String>>>,}
struct Job { name: String, log: Log,}
fn run(job: Job) { job.log.lines.lock().unwrap().push(job.name);}
fn main() { let lines = Arc::new(Mutex::new(Vec::new())); let job = Job { name: String::from("resize"), log: Log { lines: Arc::clone(&lines), }, }; thread::spawn(move || run(job)).join().unwrap(); println!("lines: {:?}", lines.lock().unwrap());}lines: ["resize"]
Generic types pass the question on to their parameters, and the notes of the next error spell out the steps:
use std::rc::Rc;use std::sync::{Arc, Mutex};
fn assert_send<T: Send>() {}
// Shared<T> holds a T behind an Arc<Mutex<..>>.struct Shared<T> { inner: Arc<Mutex<T>>,}
fn main() { assert_send::<Shared<i32>>(); // fine: Send when T is Send assert_send::<Shared<Rc<i32>>>(); // not fine: the T inside is an Rc}error[E0277]: `Rc<i32>` cannot be sent between threads safely --> src/main.rs:13:19 | 13 | assert_send::<Shared<Rc<i32>>>(); // not fine: the T inside is an Rc | ^^^^^^^^^^^^^^^ `Rc<i32>` cannot be sent between threads safely | = help: the trait `Send` is not implemented for `Rc<i32>` = note: required for `std::sync::Mutex<Rc<i32>>` to implement `Sync` = note: required for `Arc<std::sync::Mutex<Rc<i32>>>` to implement `Send` note: required because it appears within the type `Shared<Rc<i32>>` --> src/main.rs:7:8 | 7 | struct Shared<T> { | ^^^^^^ note: required by a bound in `assert_send` --> src/main.rs:4:19 | 4 | fn assert_send<T: Send>() {} | ^^^^ required by this bound in `assert_send`
An Rc is not Send, so Mutex<Rc<i32>> is not Sync (a Mutex<T> is Sync only when T is Send; see below), so Arc<Mutex<Rc<i32>>> is not Send (an Arc needs Send and Sync from its contents), so Shared<Rc<i32>> is not.
Arc<RefCell<T>> fails, Arc<Mutex<T>> works
Rc to Arc is the obvious repair, and it is not enough (Shared state shows this error first):
use std::cell::RefCell;use std::sync::Arc;use std::thread;
fn main() { let total = Arc::new(RefCell::new(0)); let handles: Vec<_> = (0..4) .map(|_| { let total = Arc::clone(&total); thread::spawn(move || *total.borrow_mut() += 1) }) .collect(); for h in handles { h.join().unwrap(); } println!("total: {}", total.borrow());}error[E0277]: `RefCell<i32>` cannot be shared between threads safely --> src/main.rs:10:27 | 10 | thread::spawn(move || *total.borrow_mut() += 1) | ------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `RefCell<i32>` cannot be shared between threads safely | | | required by a bound introduced by this call | = help: the trait `Sync` is not implemented for `RefCell<i32>` = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` instead = note: required for `Arc<RefCell<i32>>` to implement `Send` note: required because it's used within this closure --> src/main.rs:10:27 | 10 | thread::spawn(move || *total.borrow_mut() += 1) | ^^^^^^^ ... (notes pointing into the standard library trimmed)
The note says “required for Arc<RefCell<i32>> to implement Send”. Arc<T> is Send only when T is Send and Sync, and each half has a reason. Sync: every thread holding the Arc gets a &T, and RefCell changes its borrow count through &self unsynchronized. Send: Arc::into_inner hands the T itself to whichever thread holds the last Arc. So Arc<MutexGuard<i32>> is not Send, though a MutexGuard is Sync.
A Mutex<T> is Sync when T is Send, not when T is Sync. The lock makes access exclusive, so two threads never hold a &T together. But exclusive access is a &mut T, and a thread holding one can swap the T out or, for an Rc, clone it, and both leave the lock behind:
use std::mem;use std::rc::Rc;use std::sync::Mutex;
fn main() { let lock = Mutex::new(Rc::new(5)); let (copy, taken) = { let mut guard = lock.lock().unwrap(); let copy = Rc::clone(&guard); // a second owner of the same allocation let taken = mem::replace(&mut *guard, Rc::new(6)); // or the Rc itself, swapped out (copy, taken) }; // the guard is gone and the lock is free, but both Rcs are still alive println!("owners of the first Rc: {}", Rc::strong_count(©)); println!("same allocation: {}", Rc::ptr_eq(©, &taken));}owners of the first Rc: 2 same allocation: true
Two threads could each keep such a clone and update one unlocked, non-atomic count. So a Mutex makes a Send type shareable, even a Cell, and does nothing for an Rc. An RwLock lets many readers hold a &T at once, so it asks for Sync too. The version that compiles:
use std::sync::{Arc, Mutex};use std::thread;
fn main() { let total = Arc::new(Mutex::new(0)); let handles: Vec<_> = (0..4) .map(|_| { let total = Arc::clone(&total); thread::spawn(move || *total.lock().unwrap() += 1) }) .collect(); for h in handles { h.join().unwrap(); } println!("total: {}", total.lock().unwrap());}total: 4
What spawn and scope ask for
thread::spawn wants the closure to be Send + 'static, and the thread’s result to be Send + 'static (Lifetimes explains 'static). The result is a second crossing, back to the caller:
use std::rc::Rc;use std::thread;
fn main() { // the closure captures nothing, but it hands an Rc back across the thread boundary let handle = thread::spawn(|| Rc::new(5)); let value = handle.join().unwrap(); println!("{value}");}error[E0277]: `Rc<{integer}>` cannot be sent between threads safely --> src/main.rs:6:18 | 6 | let handle = thread::spawn(|| Rc::new(5)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Rc<{integer}>` cannot be sent between threads safely | = help: the trait `Send` is not implemented for `Rc<{integer}>` ... (notes pointing into the standard library trimmed)
thread::scope relaxes 'static: its threads may borrow locals, because the scope waits for them (Threads). It does not relax Send: borrowing hands every thread a &T, so T must be Sync. Atomics and a Vec are fine (Ordering::Relaxed: Shared state covers orderings):
use std::sync::atomic::{AtomicU32, Ordering};use std::thread;
fn main() { let words = vec!["alpha", "beta", "gamma"]; let hits = AtomicU32::new(0); thread::scope(|s| { for w in &words { // borrows `words` and `hits`: both are Sync, and the scope ends before they do s.spawn(|| hits.fetch_add(w.len() as u32, Ordering::Relaxed)); } }); println!("letters: {}", hits.load(Ordering::Relaxed));}letters: 14
A Cell is not:
use std::cell::Cell;use std::thread;
fn main() { let hits = Cell::new(0); thread::scope(|s| { s.spawn(|| hits.set(hits.get() + 1)); // borrows `hits`: no `move`, no 'static needed }); println!("hits: {}", hits.get());}error[E0277]: `Cell<i32>` cannot be shared between threads safely --> src/main.rs:7:17 | 7 | s.spawn(|| hits.set(hits.get() + 1)); // borrows `hits`: no `move`, no 'static needed | ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `Cell<i32>` cannot be shared between threads safely | | | required by a bound introduced by this call | = help: the trait `Sync` is not implemented for `Cell<i32>` = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicI32` instead = note: required for `&Cell<i32>` to implement `Send` note: required because it's used within this closure --> src/main.rs:7:17 | 7 | s.spawn(|| hits.set(hits.get() + 1)); // borrows `hits`: no `move`, no 'static needed | ^^ ... (notes pointing into the standard library trimmed)
“Required for &Cell<i32> to implement Send” is the Sync definition at work.
A lock guard cannot move to another thread either. Mutex is Sync, but the MutexGuard that lock() returns is not Send:
use std::sync::Mutex;use std::thread;
fn main() { let total = Mutex::new(0); thread::scope(|s| { let mut guard = total.lock().unwrap(); s.spawn(move || *guard += 1); // hands the lock itself to another thread }); println!("total: {}", total.lock().unwrap());}error[E0277]: `std::sync::MutexGuard<'_, i32>` cannot be sent between threads safely --> src/main.rs:8:17 | 8 | s.spawn(move || *guard += 1); // hands the lock itself to another thread | ----- -------^^^^^^^^^^^^ | | | | | `std::sync::MutexGuard<'_, i32>` cannot be sent between threads safely | | within this `{closure@src/main.rs:8:17: 8:24}` | required by a bound introduced by this call | = help: within `{closure@src/main.rs:8:17: 8:24}`, the trait `Send` is not implemented for `std::sync::MutexGuard<'_, i32>` note: required because it's used within this closure --> src/main.rs:8:17 | 8 | s.spawn(move || *guard += 1); // hands the lock itself to another thread | ^^^^^^^ ... (notes pointing into the standard library trimmed)
Let the thread take the lock itself:
use std::sync::Mutex;use std::thread;
fn main() { let total = Mutex::new(0); thread::scope(|s| { s.spawn(|| *total.lock().unwrap() += 1); // the thread takes the lock itself }); println!("total: {}", total.lock().unwrap());}total: 1
What a channel carries must be Send (Channels shows the error). A function that moves things to threads repeats these bounds (the third exercise), and a future held across an .await can face the same demand (Async basics).
dyn Trait + Send
A trait object forgets the concrete type, and with it Send. Box<dyn Fn() -> i32> is not Send, even if the closure inside is. You write the bound into the type (Trait objects):
use std::thread;
fn run(job: Box<dyn Fn() -> i32>) -> i32 { job()}
fn main() { let job: Box<dyn Fn() -> i32> = Box::new(|| 6 * 7); let handle = thread::spawn(move || run(job)); println!("job gave {}", handle.join().unwrap());}error[E0277]: `dyn Fn() -> i32` cannot be sent between threads safely --> src/main.rs:9:32 | 9 | let handle = thread::spawn(move || run(job)); | ------------- ^^^^^^^^^^^^^^^^ `dyn Fn() -> i32` cannot be sent between threads safely | | | required by a bound introduced by this call | = help: the trait `Send` is not implemented for `dyn Fn() -> i32` = note: required for `std::ptr::Unique<dyn Fn() -> i32>` to implement `Send` note: required because it's used within this closure --> src/main.rs:9:32 | 9 | let handle = thread::spawn(move || run(job)); | ^^^^^^^ help: use parentheses to call this trait object | 9 | let handle = thread::spawn((move || run(job))()); | + +++ ... (notes pointing into the standard library trimmed)
The help: suggests parentheses: rustc offers to call the closure because its result, an i32, would be Send. That is not what we want. The fix is + Send in the type:
use std::thread;
fn run(job: Box<dyn Fn() -> i32 + Send>) -> i32 { job()}
fn main() { let job: Box<dyn Fn() -> i32 + Send> = Box::new(|| 6 * 7); let handle = thread::spawn(move || run(job)); println!("job gave {}", handle.join().unwrap());}job gave 42
Statics must be Sync
A static lives for the whole program and every thread can reach it, so its type must be Sync:
use std::cell::Cell;
static HITS: Cell<u32> = Cell::new(0);
fn main() { HITS.set(HITS.get() + 1); println!("hits: {}", HITS.get());}error[E0277]: `Cell<u32>` cannot be shared between threads safely --> src/main.rs:3:14 | 3 | static HITS: Cell<u32> = Cell::new(0); | ^^^^^^^^^ `Cell<u32>` cannot be shared between threads safely | = help: the trait `Sync` is not implemented for `Cell<u32>` = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicU32` instead = note: shared static variables must have a type that implements `Sync`
An atomic is Sync, so four threads can add to it:
use std::sync::atomic::{AtomicU32, Ordering};use std::thread;
static HITS: AtomicU32 = AtomicU32::new(0);
fn main() { let handles: Vec<_> = (0..4) .map(|_| thread::spawn(|| HITS.fetch_add(1, Ordering::Relaxed))) .collect(); for h in handles { h.join().unwrap(); } println!("hits: {}", HITS.load(Ordering::Relaxed));}hits: 4
For state that belongs to one thread each, use thread_local! (Threads).
Fixing a Send or Sync error
Read the first line for the type that is the problem, then use the table.
| The message says | Usual fix |
|---|---|
Rc<T> cannot be sent between threads safely |
Arc<T> (Mutex<Rc<T>> does not help) |
Cell<T> or RefCell<T> cannot be shared between threads safely (also for a static) |
replace it with Mutex<T>, RwLock<T> or an atomic; wrapping is not enough (RwLock<Cell<T>> is not Sync) |
MutexGuard<..> cannot be sent between threads safely |
take the lock inside the thread |
dyn Trait cannot be sent between threads safely |
Box<dyn Trait + Send> |
*const T cannot be sent between threads safely |
a safe type; unsafe impl only if you can state why it is sound (Going deeper) |
“required because it appears within the type X” |
one field of X is the cause: follow the chain up to it |
Translating from what you know
In your language, which types may cross threads is mostly documentation or convention (this collection is not safe, immutable objects are). Rust works it out from the types and checks it at every thread call.
Going deeper
Making the promise yourself
Both traits are unsafe to implement. unsafe impl Send for T {} tells the compiler “I checked, this is fine”, and if you are wrong the result is undefined behavior (Nomicon). A raw pointer is neither Send nor Sync, which the Nomicon calls “more of a lint”: a type holding one has untracked ownership, and refusing by default stops it becoming thread-safe by accident. It is the void * of Rust, with a guard. So this type is refused even though it is harmless:
use std::thread;
// A pointer to a number that lives for the whole program.struct Table(*const i32);
impl Table { fn new(value: &'static i32) -> Table { Table(value) }
fn get(&self) -> i32 { unsafe { *self.0 } }}
static ANSWER: i32 = 42;
fn main() { let table = Table::new(&ANSWER); let handle = thread::spawn(move || table.get()); println!("answer: {}", handle.join().unwrap());}error[E0277]: `*const i32` cannot be sent between threads safely --> src/main.rs:20:32 | 20 | let handle = thread::spawn(move || table.get()); | ------------- -------^^^^^^^^^^^^ | | | | | `*const i32` cannot be sent between threads safely | | within this `{closure@src/main.rs:20:32: 20:39}` | required by a bound introduced by this call | = help: within `{closure@src/main.rs:20:32: 20:39}`, the trait `Send` is not implemented for `*const i32` note: required because it appears within the type `Table` --> src/main.rs:4:8 | 4 | struct Table(*const i32); | ^^^^^ note: required because it's used within this closure --> src/main.rs:20:32 | 20 | let handle = thread::spawn(move || table.get()); | ^^^^^^^ ... (notes pointing into the standard library trimmed)
This version’s claim covers every Table that can exist: the field is private and new takes a &'static i32, which is shared, read-only, never freed, and already Send and Sync. (Contrived: a &'static i32 field needs no unsafe. It shows the form.)
use std::thread;
mod table { /// A read-only view of a number that lives for the whole program. pub struct Table(*const i32); // the field is private
impl Table { pub fn new(value: &'static i32) -> Table { Table(value) // the only way to make a Table }
pub fn get(&self) -> i32 { unsafe { *self.0 } } }
// SAFETY: every Table holds a pointer made from a `&'static i32`: shared, read-only // and never freed. A `&'static i32` is already Send and Sync, so this is too. unsafe impl Send for Table {} unsafe impl Sync for Table {}}
static ANSWER: i32 = 42;
fn main() { let table = table::Table::new(&ANSWER); thread::scope(|s| { let a = s.spawn(|| table.get()); // shares &Table: needs Sync let b = s.spawn(|| table.get()); println!("answers: {} and {}", a.join().unwrap(), b.join().unwrap()); }); let moved = thread::spawn(move || table.get()); // moves Table: needs Send println!("moved: {}", moved.join().unwrap());}answers: 42 and 42 moved: 42
unsafe has its own lesson; the point is the size of the promise: nothing checks it, and it must hold for every value the type can have.
To go the other way, impl !Send for T {} would be the direct form, but the compiler refuses it on stable:
struct Handle(u32);
impl !Send for Handle {}
fn main() { let h = Handle(1); println!("{}", h.0);}error[E0658]: negative impls are experimental --> src/main.rs:3:6 | 3 | impl !Send for Handle {} | ^^^^^ | = note: see issue #68318 <https://github.com/rust-lang/rust/issues/68318> for more information = help: use marker types for now
What works today is a field that is not Send. PhantomData<T> stores no data; the compiler treats the struct as if it held a T, and its Send and Sync follow T’s (docs). So PhantomData<*const ()> removes both traits (Pinned in check-table.rs).
What they do not prevent
The Nomicon is exact: safe Rust has no data races, and does not prevent “general race conditions”. It also calls deadlock “safe” (Shared state runs both). That page’s check-then-act example uses an Arc<Mutex<i32>>, which the table shows is Send and Sync. Every access is locked, the types raise no objection, and two threads still lose an update, because the check and the change are separate steps. The cure is a design: one guard around both. The marker traits answer “may these threads touch this value”, never “is the sequence of touches right”.
Practice
Try it
Each exercise has checks in main: replace the todo!() calls and run until it prints all checks passed. The starters compile and fail when run.
Send, Sync, or neither
Fill in guesses(): for ten types, say whether each is Send and whether it is Sync. None is a row of the table above, so derive each from its fields and the rules (two are structs in the file). The checks name the wrong type, not the trait or the answer, and guesses cannot call the probes, which live inside main.
#![forbid(unsafe_code)]#![allow(dead_code)]
use std::cell::Cell;use std::marker::PhantomData;use std::rc::Rc;use std::sync::{Arc, Mutex, MutexGuard};
struct Config { name: String, retries: Cell<u32>,}
struct Handle { id: u32, cache: Arc<Mutex<Vec<String>>>,}
/// For each type, in this order, say `(is it Send, is it Sync)`:/// 0 Config 5 Arc<Mutex<Cell<i32>>>/// 1 Handle 6 &mut Cell<i32>/// 2 Option<Rc<i32>> 7 Box<dyn Fn() + Sync>/// 3 (i32, *const u8) 8 Vec<MutexGuard<'_, i32>>/// 4 Mutex<Rc<i32>> 9 &Mutex<Cell<i32>>fn guesses() -> [(bool, bool); 10] { todo!("one (send, sync) pair per type; derive each from its fields and the rules on the page")}
fn main() { // These probes belong to the checks: they are defined inside `main`, so `guesses` // cannot call them. Predict first, then run. macro_rules! probe { ($name:ident, $bound:ident) => { macro_rules! $name { ($t:ty) => {{ struct Probe<T: ?Sized>(PhantomData<T>); trait No { fn answer(&self) -> bool { false } } impl<T: ?Sized> No for Probe<T> {} impl<T: ?Sized + $bound> Probe<T> { fn answer(&self) -> bool { true } } Probe::<$t>(PhantomData).answer() }}; } }; } probe!(is_send, Send); probe!(is_sync, Sync);
macro_rules! actual { ($t:ty) => { (is_send!($t), is_sync!($t)) }; } let actual = [ actual!(Config), actual!(Handle), actual!(Option<Rc<i32>>), actual!((i32, *const u8)), actual!(Mutex<Rc<i32>>), actual!(Arc<Mutex<Cell<i32>>>), actual!(&'static mut Cell<i32>), actual!(Box<dyn Fn() + Sync>), actual!(Vec<MutexGuard<'static, i32>>), actual!(&'static Mutex<Cell<i32>>), ]; let names = [ "Config", "Handle", "Option<Rc<i32>>", "(i32, *const u8)", "Mutex<Rc<i32>>", "Arc<Mutex<Cell<i32>>>", "&mut Cell<i32>", "Box<dyn Fn() + Sync>", "Vec<MutexGuard<'_, i32>>", "&Mutex<Cell<i32>>", ];
let guessed = guesses(); for i in 0..names.len() { // `assert!`, not `assert_eq!`: a failure must not print the right answer assert!( guessed[i] == actual[i], "wrong guess for {}: look at both Send and Sync", names[i] ); } println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
#![forbid(unsafe_code)]#![allow(dead_code)]
use std::cell::Cell;use std::marker::PhantomData;use std::rc::Rc;use std::sync::{Arc, Mutex, MutexGuard};
struct Config { name: String, retries: Cell<u32>,}
struct Handle { id: u32, cache: Arc<Mutex<Vec<String>>>,}
/// For each type, in this order, say `(is it Send, is it Sync)`:/// 0 Config 5 Arc<Mutex<Cell<i32>>>/// 1 Handle 6 &mut Cell<i32>/// 2 Option<Rc<i32>> 7 Box<dyn Fn() + Sync>/// 3 (i32, *const u8) 8 Vec<MutexGuard<'_, i32>>/// 4 Mutex<Rc<i32>> 9 &Mutex<Cell<i32>>fn guesses() -> [(bool, bool); 10] { [ (true, false), // Config: a String and a Cell; the Cell may move but not be shared (true, true), // Handle: a u32 and an Arc<Mutex<Vec<String>>>, which is both (false, false), // Option<Rc<..>> is as good as its Rc (false, false), // a raw pointer is neither, and a tuple needs every part (false, false), // Mutex<T> is Sync only if T is Send; an Rc is not (true, true), // Mutex<Cell<..>> is Sync (a Cell is Send); the Arc then needs both (true, false), // &mut T: Send if T is Send, Sync if T is Sync; a Cell is only the first (false, true), // the trait object says Sync and nothing about Send (false, true), // a MutexGuard is Sync but not Send; a Vec is as good as its elements (true, true), // &T is Send when T is Sync, and Mutex<Cell<..>> is Sync ]}
fn main() { // These probes belong to the checks: they are defined inside `main`, so `guesses` // cannot call them. Predict first, then run. macro_rules! probe { ($name:ident, $bound:ident) => { macro_rules! $name { ($t:ty) => {{ struct Probe<T: ?Sized>(PhantomData<T>); trait No { fn answer(&self) -> bool { false } } impl<T: ?Sized> No for Probe<T> {} impl<T: ?Sized + $bound> Probe<T> { fn answer(&self) -> bool { true } } Probe::<$t>(PhantomData).answer() }}; } }; } probe!(is_send, Send); probe!(is_sync, Sync);
macro_rules! actual { ($t:ty) => { (is_send!($t), is_sync!($t)) }; } let actual = [ actual!(Config), actual!(Handle), actual!(Option<Rc<i32>>), actual!((i32, *const u8)), actual!(Mutex<Rc<i32>>), actual!(Arc<Mutex<Cell<i32>>>), actual!(&'static mut Cell<i32>), actual!(Box<dyn Fn() + Sync>), actual!(Vec<MutexGuard<'static, i32>>), actual!(&'static Mutex<Cell<i32>>), ]; let names = [ "Config", "Handle", "Option<Rc<i32>>", "(i32, *const u8)", "Mutex<Rc<i32>>", "Arc<Mutex<Cell<i32>>>", "&mut Cell<i32>", "Box<dyn Fn() + Sync>", "Vec<MutexGuard<'_, i32>>", "&Mutex<Cell<i32>>", ];
let guessed = guesses(); for i in 0..names.len() { // `assert!`, not `assert_eq!`: a failure must not print the right answer assert!( guessed[i] == actual[i], "wrong guess for {}: look at both Send and Sync", names[i] ); } println!("all checks passed");}all checks passed
A counter that threads can share
Write hit_from_threads: threads scoped threads that each call counter.hit() times times. With Counter as it is, the compiler refuses it (E0277), because the count lives in a Cell. Change that one field until it is accepted. Counter records which threads called hit, so a loop on one thread cannot pass, and every Counter counts separately, so a static cannot either. The file forbids unsafe. An atomic and a Mutex both pass: pick one and say why it is Sync.
#![forbid(unsafe_code)]#![allow(dead_code)]
use std::cell::Cell;use std::collections::HashSet;use std::marker::PhantomData;use std::sync::Mutex;use std::thread::{self, ThreadId};
// `is_sync!(T)` asks the compiler whether `T: Sync` and gives back a bool.macro_rules! is_sync { ($t:ty) => {{ struct Probe<T: ?Sized>(PhantomData<T>); trait No { fn answer(&self) -> bool { false } } impl<T: ?Sized> No for Probe<T> {} impl<T: ?Sized + Sync> Probe<T> { fn answer(&self) -> bool { true } } Probe::<$t>(PhantomData).answer() }};}
/// Counts hits. Several threads must be able to call `hit` on one shared `Counter`.struct Counter { hits: Cell<u32>, /// Which threads called `hit`. It is already shared safely: leave it alone. threads: Mutex<HashSet<ThreadId>>,}
impl Counter { fn new() -> Counter { Counter { hits: Cell::new(0), threads: Mutex::new(HashSet::new()), } }
fn hit(&self) { self.hits.set(self.hits.get() + 1); self.threads.lock().unwrap().insert(thread::current().id()); }
fn total(&self) -> u32 { self.hits.get() }
fn threads_seen(&self) -> usize { self.threads.lock().unwrap().len() }}
/// Starts `threads` scoped threads that each call `counter.hit()` `times` times, and/// waits for all of them.fn hit_from_threads(counter: &Counter, threads: usize, times: u32) { todo!("thread::scope, then one s.spawn per thread; each calls counter.hit() `times` times")}
fn main() { let counter = Counter::new(); hit_from_threads(&counter, 8, 1000); assert_eq!( counter.threads_seen(), 8, "the hits must come from 8 different threads" ); assert_eq!(counter.total(), 8000, "no hit may be lost"); assert!( is_sync!(Counter), "Counter must be Sync to be shared by reference" );
// a second counter is separate from the first let other = Counter::new(); other.hit(); assert_eq!(other.total(), 1); assert_eq!(other.threads_seen(), 1); assert_eq!(counter.total(), 8000);
// the main thread can still use a counter after the threads are done counter.hit(); assert_eq!(counter.total(), 8001);
// zero threads do nothing; zero hits per thread do nothing hit_from_threads(&other, 0, 5); hit_from_threads(&other, 3, 0); assert_eq!(other.total(), 1); hit_from_threads(&other, 3, 2); assert_eq!(other.total(), 7); assert_eq!(other.threads_seen(), 4);
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
#![forbid(unsafe_code)]#![allow(dead_code)]
use std::collections::HashSet;use std::marker::PhantomData;use std::sync::Mutex;use std::sync::atomic::{AtomicU32, Ordering};use std::thread::{self, ThreadId};
// `is_sync!(T)` asks the compiler whether `T: Sync` and gives back a bool.macro_rules! is_sync { ($t:ty) => {{ struct Probe<T: ?Sized>(PhantomData<T>); trait No { fn answer(&self) -> bool { false } } impl<T: ?Sized> No for Probe<T> {} impl<T: ?Sized + Sync> Probe<T> { fn answer(&self) -> bool { true } } Probe::<$t>(PhantomData).answer() }};}
/// Counts hits. Several threads must be able to call `hit` on one shared `Counter`.struct Counter { hits: AtomicU32, /// Which threads called `hit`. It is already shared safely: leave it alone. threads: Mutex<HashSet<ThreadId>>,}
impl Counter { fn new() -> Counter { Counter { hits: AtomicU32::new(0), threads: Mutex::new(HashSet::new()), } }
fn hit(&self) { self.hits.fetch_add(1, Ordering::Relaxed); self.threads.lock().unwrap().insert(thread::current().id()); }
fn total(&self) -> u32 { self.hits.load(Ordering::Relaxed) }
fn threads_seen(&self) -> usize { self.threads.lock().unwrap().len() }}
/// Starts `threads` scoped threads that each call `counter.hit()` `times` times, and/// waits for all of them.fn hit_from_threads(counter: &Counter, threads: usize, times: u32) { thread::scope(|s| { for _ in 0..threads { s.spawn(|| { for _ in 0..times { counter.hit(); } }); } });}
fn main() { let counter = Counter::new(); hit_from_threads(&counter, 8, 1000); assert_eq!( counter.threads_seen(), 8, "the hits must come from 8 different threads" ); assert_eq!(counter.total(), 8000, "no hit may be lost"); assert!( is_sync!(Counter), "Counter must be Sync to be shared by reference" );
// a second counter is separate from the first let other = Counter::new(); other.hit(); assert_eq!(other.total(), 1); assert_eq!(other.threads_seen(), 1); assert_eq!(counter.total(), 8000);
// the main thread can still use a counter after the threads are done counter.hit(); assert_eq!(counter.total(), 8001);
// zero threads do nothing; zero hits per thread do nothing hit_from_threads(&other, 0, 5); hit_from_threads(&other, 3, 0); assert_eq!(other.total(), 1); hit_from_threads(&other, 3, 2); assert_eq!(other.total(), 7); assert_eq!(other.threads_seen(), 4);
println!("all checks passed");}all checks passed
Run every job on its own thread
Write run_all. The signature lacks the bounds thread::spawn demands of F and T: add them, guided by the compiler. Results must come back in job order, and the jobs must run at the same time: the checks use jobs that each wait for the other three to start.
#![forbid(unsafe_code)]
use std::sync::Arc;use std::sync::atomic::{AtomicUsize, Ordering};use std::thread;
/// Runs every job on a thread of its own, all at the same time, and returns the results in/// the order of `jobs`. Threads are started first and joined afterwards.////// The bounds are the part to get right: what must hold for `F` and `T` so that the jobs/// and their results may cross to another thread?fn run_all<T, F>(jobs: Vec<F>) -> Vec<T>where F: FnOnce() -> T,{ todo!("spawn one thread per job, keep the handles, join them in order")}
fn main() { // results come back in job order, and jobs may own data (a String each) let names = ["ann", "bo", "cyd"]; let jobs: Vec<_> = names .iter() .map(|name| { let name = name.to_string(); move || format!("hello {name}") }) .collect(); assert_eq!(run_all(jobs), ["hello ann", "hello bo", "hello cyd"]);
// no jobs, no threads let none: Vec<fn() -> u32> = Vec::new(); assert!(run_all(none).is_empty());
// The jobs must really run at the same time: each one waits (a bounded number of // polite spins) until all four have started. Run one after another, the first job // gives up waiting and reports false. let arrived = Arc::new(AtomicUsize::new(0)); let jobs: Vec<_> = (0..4) .map(|_| { let arrived = Arc::clone(&arrived); move || { arrived.fetch_add(1, Ordering::SeqCst); for _ in 0..200_000 { if arrived.load(Ordering::SeqCst) == 4 { return true; } thread::yield_now(); } false } }) .collect(); assert_eq!( run_all(jobs), [true, true, true, true], "the four jobs must run concurrently, not one after another" );
// a job that runs on another thread really is on another thread let main_id = thread::current().id(); let jobs: Vec<_> = (0..3).map(|_| move || thread::current().id()).collect(); for id in run_all(jobs) { assert_ne!(id, main_id); }
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
#![forbid(unsafe_code)]
use std::sync::Arc;use std::sync::atomic::{AtomicUsize, Ordering};use std::thread;
/// Runs every job on a thread of its own, all at the same time, and returns the results in/// the order of `jobs`. Threads are started first and joined afterwards.////// The bounds are the part to get right: what must hold for `F` and `T` so that the jobs/// and their results may cross to another thread?fn run_all<T, F>(jobs: Vec<F>) -> Vec<T>where F: FnOnce() -> T + Send + 'static, T: Send + 'static,{ let handles: Vec<_> = jobs.into_iter().map(|job| thread::spawn(job)).collect(); handles.into_iter().map(|h| h.join().unwrap()).collect()}
fn main() { // results come back in job order, and jobs may own data (a String each) let names = ["ann", "bo", "cyd"]; let jobs: Vec<_> = names .iter() .map(|name| { let name = name.to_string(); move || format!("hello {name}") }) .collect(); assert_eq!(run_all(jobs), ["hello ann", "hello bo", "hello cyd"]);
// no jobs, no threads let none: Vec<fn() -> u32> = Vec::new(); assert!(run_all(none).is_empty());
// The jobs must really run at the same time: each one waits (a bounded number of // polite spins) until all four have started. Run one after another, the first job // gives up waiting and reports false. let arrived = Arc::new(AtomicUsize::new(0)); let jobs: Vec<_> = (0..4) .map(|_| { let arrived = Arc::clone(&arrived); move || { arrived.fetch_add(1, Ordering::SeqCst); for _ in 0..200_000 { if arrived.load(Ordering::SeqCst) == 4 { return true; } thread::yield_now(); } false } }) .collect(); assert_eq!( run_all(jobs), [true, true, true, true], "the four jobs must run concurrently, not one after another" );
// a job that runs on another thread really is on another thread let main_id = thread::current().id(); let jobs: Vec<_> = (0..3).map(|_| move || thread::current().id()).collect(); for id in run_all(jobs) { assert_ne!(id, main_id); }
println!("all checks passed");}all checks passed
Read more
- The Rust Book, chapter 16.4: Extensible Concurrency with
SyncandSend. - The standard library:
Send,Sync,thread::scope,Mutex. - The Rustonomicon: Data races and race conditions and Send and Sync.
- Errors: E0277, E0658, or this site’s index.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.