Send vs. Sync: Rust's Thread Safety Contracts
Send means a value can move to another thread; Sync means references to it can be shared. They are the compiler's contracts for preventing data races. The compiler checks them when you spawn threads.
WHY IT EXISTS: To prevent data races at compile time. In systems programming, managing shared mutable state across threads is a primary source of bugs. Rust uses the Send and Sync traits to statically verify that data passed between threads is safe to use concurrently, eliminating an entire class of concurrency errors.
THE MENTAL MODEL: Think of Send and Sync as permissions for concurrency. Send is a "move permit," allowing a value's ownership to cross a thread boundary. Sync is a "share permit," allowing multiple threads to hold a reference (&T) to the same value. A simple rule connects them: a type T is Sync if and only if a reference to it, &T, is Send.
HOW IT WORKS: Send and Sync are special "auto traits." The compiler automatically derives them for your types if all their fields are also Send and Sync. Most primitive types are Send and Sync. However, types designed for single-threaded use are not. For example, Rc<T> (non-atomic reference counting) is neither Send nor Sync. UnsafeCell<T> (and thus Cell<T> and RefCell<T>) is not Sync. Raw pointers (*mut T) are also neither Send nor Sync by default as a safety precaution, preventing types that contain them from being automatically marked thread-safe.
WHEN TO USE IT: You don't typically "use" them directly; the Rust compiler does it for you. You'll encounter them in trait bounds for concurrent functions, like thread::spawn. Its signature requires the closure and its return value to be Send, ensuring they can be safely moved to the new thread. If the compiler reports that a type doesn't implement Send or Sync, it's a signal that you might cause a data race.
WHEN NOT TO USE IT: You should not manually implement Send or Sync unless you are wrapping an unsafe type (like a raw pointer) and have implemented the necessary synchronization (e.g., using atomics or mutexes) to make it genuinely thread-safe. This requires an unsafe block and a deep understanding of the guarantees you must provide. Incorrectly implementing these traits breaks Rust's safety guarantees and invites undefined behavior.
ONE CANONICAL EXAMPLE: The smart pointer Arc<T> (Atomically-Referenced Counter) is a thread-safe version of Rc<T>. Internally, it uses a raw pointer and atomic operations for its reference count. Because the counting is atomic, it's safe to share across threads. Therefore, the standard library implements Send and Sync for Arc<T> (when T is also Send and Sync), demonstrating how to build a safe abstraction on top of unsafe primitives that correctly satisfies these crucial traits.
Read the original → doc.rust-lang.org
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.