Skip to content

Send and Sync

JavaScript sidesteps thread-safety entirely — there is only one thread (per Worker), so the question of “can two threads concurrently access this value?” simply does not arise. There is no type-level concept of thread safety in TypeScript.

Rust supports real parallelism, which means two threads can genuinely access the same memory at the same time. To make this safe without a runtime garbage collector or a lock, Rust uses marker traits: Send and Sync.

TraitMeaningExample types
SendA value of type T can be moved to another threadi32, String, Vec<T>, Arc<T>
SyncA shared reference &T can be sent to another threadi32, Arc<T>, Mutex<T>
Not SendCannot move to another threadRc<T> (non-atomic reference count)
Not SyncCannot share a reference across threadsCell<T>, RefCell<T>

These traits are auto-implemented by the compiler based on the types inside your struct. You almost never implement them manually. The rules propagate: if your struct contains a field that is not Send, your struct is not Send either.

TypeScript
// TypeScript has no thread-safety types.
// The language guarantees safety by having only one thread per Worker.
// There is no type-level concept of Send or Sync.
// In a Worker, you can only communicate via structured-clone (postMessage):
// plain objects, arrays, ArrayBuffer, etc.
// You CANNOT pass a class instance with methods, a closure, or a WeakRef.
const worker = new Worker('./worker.js');
// This WORKS: number is structured-cloneable
worker.postMessage(42);
// This FAILS at runtime: functions are not cloneable
// worker.postMessage(() => 42); // DataCloneError
Rust
// Rust: Send/Sync are compile-time guarantees, not runtime checks.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
// Arc<Mutex<T>> is both Send and Sync:
// - Arc: thread-safe reference counting (vs Rc which is NOT Send)
// - Mutex: provides interior mutability with a lock
let shared: Arc<Mutex<Vec<u32>>> = Arc::new(Mutex::new(vec![]));
let mut handles = vec![];
for i in 0..4 {
let data = Arc::clone(&shared); // cheap clone of the Arc pointer
let h = thread::spawn(move || {
data.lock().unwrap().push(i); // lock, push, auto-unlock
});
handles.push(h);
}
for h in handles { h.join().unwrap(); }
let mut result = shared.lock().unwrap().clone();
result.sort();
println!("collected: {:?}", result); // collected: [0, 1, 2, 3]
}
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let data: Arc<Mutex<Vec<u32>>> = Arc::new(Mutex::new(vec![]));
let mut handles = vec![];
for i in 0..4 {
let d = Arc::clone(&data);
let h = thread::spawn(move || {
d.lock().unwrap().push(i);
});
handles.push(h);
}
for h in handles {
h.join().unwrap();
}
let mut result = data.lock().unwrap().clone();
result.sort();
println!("collected: {:?}", result);
}
What does the `Send` marker trait mean in Rust?
Why is `Rc<T>` not `Send`, but `Arc<T>` is?
When are Send/Sync violations detected in Rust?