Skip to content
Learn Rust
Theme

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

Rustsrc/main.rsRun ↗
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}");
}
$ cargo run
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])
}
$ go run main.go
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()
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar
moved there and back: ["alpha", "beta"]
lengths read in parallel: 5 and 4
import json
from 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()}")
$ python3 main.py
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);
}
$ tsc --strict --module nodenext --types node main.ts && node main.js
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());
}
}
}
$ javac Main.java && java Main
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 Task
var 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 safe
var a = Task.Run(() => words[0].Length);
var b = Task.Run(() => words[1].Length);
Console.WriteLine($"lengths read in parallel: {await a} and {await b}");
$ dotnet run
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;
}
$ gcc -Wall -Wextra main.c -o main && ./main
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";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main
moved there and back: ["alpha", "beta"]
lengths read in parallel: 5 and 4

What's different

  • Send means a value may be moved to another thread. Sync means a &T may be shared between threads; the standard library defines it as "T is Sync if and only if &T is Send". Both are marker traits with no methods.
  • The compiler implements them for you, field by field: a struct is Send when every field is. One Rc anywhere inside makes the whole struct not Send, and the error names the chain from the thread call down to the Rc.
  • thread::spawn needs the closure and the thread's result to be Send (and 'static). Arc<T> is Send only when T is Send and Sync: RefCell<i32> is not Sync, Mutex<i32> is, which is why the shared-counter pattern is Arc<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.
Gotcha

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.
Rustsrc/main.rs✗ won't compile
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)
}
$ go run main.go✓ runs
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\"" })
}
$ kotlinc Main.kt -include-runtime -d main.jar && java -jar main.jar✓ runs
log: ["resize"]
import json
import 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))
$ python3 main.py✓ runs
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");
}
$ tsc --strict --module nodenext --types node main.ts && node main.js✓ runs
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) + "\"]");
}
}
$ javac Main.java && java Main✓ runs
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);
$ dotnet run✓ runs
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;
}
$ gcc -Wall -Wextra main.c -o main && ./main✓ runs
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";
}
$ g++ -std=c++20 -Wall -Wextra main.cpp -o main && ./main✓ runs
log: ["resize"]
What does the compiler say? Guess first, then open.
$ cargo buildWhat is E0277?
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)
What happens in Go

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.

What happens in Kotlin

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

What happens in Python

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

What happens in TypeScript

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.

What happens in Java

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.

What happens in C#

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.

What happens in C

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.

What happens in C++

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.

What Rust sees

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.

Why Rust says no

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.

Rust, fixedsrc/main.rsRun ↗
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());
}
$ cargo run
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 &T may be used by several threads at once. The standard library’s definition is “T is Sync if and only if &T is Send”.

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:

Rustsrc/main.rsRun ↗
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);
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
use std::rc::Rc;
fn assert_send<T: Send>() {}
fn main() {
assert_send::<Rc<i32>>();
}
$ cargo buildWhat is E0277?
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:

Rustsrc/main.rs✗ won't compile
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
}
$ cargo buildWhat is E0277?
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’s send, a thread’s return value. That needs Send.
  • Several threads look at it through a reference: a thread::scope borrow, a static, the T inside an Arc<T>. That needs Sync. (The Arc itself is moved too, so it needs Send.)
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):

Rustsrc/main.rs✗ won't compile
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();
}
$ cargo buildWhat is E0277?
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:

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
lines: ["resize"]

Generic types pass the question on to their parameters, and the notes of the next error spell out the steps:

Rustsrc/main.rs✗ won't compile
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
}
$ cargo buildWhat is E0277?
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):

Rustsrc/main.rs✗ won't compile
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());
}
$ cargo buildWhat is E0277?
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:

Rustsrc/main.rsRun ↗
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(&copy));
println!("same allocation: {}", Rc::ptr_eq(&copy, &taken));
}
$ cargo run
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:

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
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}");
}
$ cargo buildWhat is E0277?
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):

Rustsrc/main.rsRun ↗
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));
}
$ cargo run
letters: 14

A Cell is not:

Rustsrc/main.rs✗ won't compile
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());
}
$ cargo buildWhat is E0277?
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:

Rustsrc/main.rs✗ won't compile
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());
}
$ cargo buildWhat is E0277?
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:

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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):

Rustsrc/main.rs✗ won't compile
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());
}
$ cargo buildWhat is E0277?
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:

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
use std::cell::Cell;
static HITS: Cell<u32> = Cell::new(0);
fn main() {
HITS.set(HITS.get() + 1);
println!("hits: {}", HITS.get());
}
$ cargo buildWhat is E0277?
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:

Rustsrc/main.rsRun ↗
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));
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
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());
}
$ cargo buildWhat is E0277?
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.)

Rustsrc/main.rsRun ↗
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());
}
$ cargo run
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:

Rustsrc/main.rs✗ won't compile
struct Handle(u32);
impl !Send for Handle {}
fn main() {
let h = Handle(1);
println!("{}", h.0);
}
$ cargo buildWhat is E0658?
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.

Starterexercises/guess.rs
#![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
One solutionexercises/guess.solution.rsRun ↗
#![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");
}
$ cargo run
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.

Starterexercises/counter.rs
#![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
One solutionexercises/counter.solution.rsRun ↗
#![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");
}
$ cargo run
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.

Starterexercises/run-all.rs
#![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
One solutionexercises/run-all.solution.rsRun ↗
#![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");
}
$ cargo run
all checks passed

Read more