Send and Sync in Rust explained
What the Send and Sync traits mean in Rust, which types are not Send or Sync (Rc, RefCell, Cell), and how to fix 'cannot be sent between threads safely'.
Rust prevents data races at compile time with two marker traits (traits with no methods that describe a property of a type):
Send: a value of this type can be moved to another thread.Sync: a value of this type can be shared between threads by reference. Precisely,TisSyncwhen&TisSend.
You almost never implement them. They're auto traits: the compiler implements them for your struct or enum automatically when all its fields are Send / Sync. A struct containing an Rc is not Send, because Rc isn't.
Which types are which
| Type | Send | Sync | Why |
|---|---|---|---|
i32, String, Vec<T>, Box<T> (of Send/Sync data) |
yes | yes | plain owned data |
Arc<T> (T: Send + Sync) |
yes | yes | atomic reference count |
Mutex<T> (T: Send) |
yes | yes | the lock serializes access |
Rc<T> |
no | no | non-atomic reference count |
RefCell<T>, Cell<T> |
yes | no | unsynchronized interior mutability |
MutexGuard<T> |
no | yes | must be unlocked on the thread that locked it |
raw pointers *const T, *mut T |
no | no | the compiler can't check what they point to |
The errors
thread::spawn requires its closure (and everything it captures) to be Send + 'static. Moving an Rc into a thread fails:
use std::rc::Rc;
use std::thread;
fn main() {
let data = Rc::new(5);
let h = thread::spawn(move || {
println!("{data}");
});
h.join().unwrap();
}
error[E0277]: `Rc<i32>` cannot be sent between threads safely
The note explains: "within {closure}, the trait Send is not implemented for Rc<i32>". Fix: use Arc.
Sharing a RefCell through an Arc fails on Sync:
use std::cell::RefCell;
use std::sync::Arc;
use std::thread;
fn main() {
let data = Arc::new(RefCell::new(5));
let d = Arc::clone(&data);
let h = thread::spawn(move || {
*d.borrow_mut() += 1;
});
h.join().unwrap();
}
error[E0277]: `RefCell<i32>` cannot be shared between threads safely
Arc<T> is only Send if T is Sync, and RefCell isn't (two threads could both call borrow_mut at once). Fix: Arc<Mutex<T>>, Arc<RwLock<T>>, or an atomic.
The thread-safe equivalents
| Single-threaded | Thread-safe |
|---|---|
Rc<T> |
Arc<T> |
RefCell<T> |
Mutex<T> or RwLock<T> |
Cell<u32> |
AtomicU32 |
Rc<RefCell<T>> |
Arc<Mutex<T>> |
Send in async code
tokio::spawn also requires Send futures, because a task can move between worker threads at each .await. Holding an Rc or a std::sync::MutexGuard across an .await makes the future non-Send, and the error reads "future cannot be sent between threads safely". Drop such values before awaiting, or use Arc and tokio::sync::Mutex.
unsafe impl Send
Types wrapping raw pointers (FFI handles, custom data structures) can promise thread safety with unsafe impl Send for MyType {}. It's unsafe because the compiler can no longer check it: you're vouching that it's correct. In application code, you should essentially never need it.
Example
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use std::thread;
fn assert_send<T: Send>(name: &str) {
println!("{name} is Send");
}
fn assert_sync<T: Sync>(name: &str) {
println!("{name} is Sync");
}
struct Stats {
hits: AtomicU32,
names: Mutex<Vec<String>>,
}
fn main() {
assert_send::<String>("String");
assert_sync::<Arc<Mutex<Vec<i32>>>>("Arc<Mutex<Vec<i32>>>");
assert_send::<RefCell<i32>>("RefCell<i32>"); // Send, but not Sync
assert_sync::<Stats>("Stats (all fields are Sync)");
// Single-threaded sharing: Rc<RefCell<T>>.
let local = Rc::new(RefCell::new(vec![1]));
Rc::clone(&local).borrow_mut().push(2);
println!("local {:?}", local.borrow());
// The thread-safe version: Arc with a Mutex and an atomic.
let stats = Arc::new(Stats { hits: AtomicU32::new(0), names: Mutex::new(Vec::new()) });
let handles: Vec<_> = ["ferris", "corro", "gopher"]
.into_iter()
.map(|name| {
let stats = Arc::clone(&stats);
thread::spawn(move || {
stats.hits.fetch_add(1, Ordering::Relaxed);
stats.names.lock().unwrap().push(name.to_uppercase());
})
})
.collect();
for h in handles {
h.join().unwrap();
}
let mut names = stats.names.lock().unwrap().clone();
names.sort();
println!("{} hits from {names:?}", stats.hits.load(Ordering::Relaxed));
}
Output:
String is Send
Arc<Mutex<Vec<i32>>> is Sync
RefCell<i32> is Send
Stats (all fields are Sync) is Sync
local [1, 2]
3 hits from ["CORRO", "FERRIS", "GOPHER"]