RustArena

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):

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"]

Practice it