22.10 The Send and Sync Marker Traits
Two crucial marker traits underpin Rust’s compile-time concurrency safety: Send and Sync. They don’t define any methods; their purpose is to “mark” types with specific properties related to thread safety. The compiler automatically implements (or doesn’t implement) these traits for user-defined types based on their composition.
-
Send: A typeTisSendif a value of typeTcan be safely transferred (moved) to another thread.- Most primitive types (
i32,bool,f64, etc.) areSend. - Owned container types like
String,Vec<T>,Box<T>areSendif their contained typeTis alsoSend. Arc<T>isSendifTisSend + Sync(shared ownership requires the inner type to be sharable too).Mutex<T>andRwLock<T>areSendifTisSend.- Types that are not inherently
Send:Rc<T>: Its reference counting is non-atomic, making it unsafe to transfer ownership across threads where counts could be updated concurrently.- Raw pointers (
*const T,*mut T): They don’t have safety guarantees, so they are notSendby default. Types containing raw pointers need careful consideration, often requiringunsafe impl Send.
- Most primitive types (
-
Sync: A typeTisSyncif a shared reference&Tcan be safely shared across multiple threads concurrently.- Technically,
TisSyncif and only if&T(a shared reference toT) isSend. - Most primitive types are
Sync. - Immutable types composed of
Synctypes are typicallySync. Arc<T>isSyncifTisSend + Sync.Mutex<T>isSyncifTisSend. Even though theMutexallows mutation ofT, it synchronizes access, making it safe to share&Mutex<T>across threads. Access to the innerTis controlled via the lock, which provides exclusive access.RwLock<T>isSyncifTisSend + Sync(for readers) andTisSend(for writers). A shared reference&RwLock<T>allows multiple threads to acquire shared read locks, but only one thread to acquire an exclusive write lock.- Types that are not inherently
Sync:Cell<T>,RefCell<T>: These provide interior mutability without thread synchronization, making it unsafe to share&Cell<T>or&RefCell<T>across threads, as concurrent mutations could lead to data races.Rc<T>: Non-atomic reference counting makes sharing&Rc<T>unsafe.- Raw pointers (
*const T,*mut T): NotSyncby default.
- Technically,
The compiler uses these traits implicitly when checking thread-related operations:
- The closure passed to
std::thread::spawnmust beSendbecause it might be moved to a new thread. Any captured variables must also beSend. - Data shared using
Arc<T>requiresT: Send + Syncbecause multiple threads might access it concurrently via shared references derived from theArc. - Attempting to use a non-
Sendtype across threads (e.g., putting anRc<T>inside anArcand sending it to another thread) will result in a compile-time error. - Attempting to share a non-
Synctype (e.g.,Arc<RefCell<T>>) across threads where multiple threads could potentially access it concurrently will also result in a compile-time error.
Understanding Send and Sync helps clarify why the Rust compiler allows certain concurrent patterns while forbidding others, forming the foundation of its “fearless concurrency” guarantee against data races in safe code.