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 typeT
isSend
if a value of typeT
can be safely transferred (moved) to another thread.- Most primitive types (
i32
,bool
,f64
, etc.) areSend
. - Owned container types like
String
,Vec<T>
,Box<T>
areSend
if their contained typeT
is alsoSend
. Arc<T>
isSend
ifT
isSend + Sync
(shared ownership requires the inner type to be sharable too).Mutex<T>
andRwLock<T>
areSend
ifT
isSend
.- 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 notSend
by default. Types containing raw pointers need careful consideration, often requiringunsafe impl Send
.
- Most primitive types (
-
Sync
: A typeT
isSync
if a reference&T
can be safely shared across multiple threads concurrently.- Technically,
T
isSync
if and only if&T
(an immutable reference toT
) isSend
. - Most primitive types are
Sync
. - Immutable types composed of
Sync
types are typicallySync
. Arc<T>
isSync
ifT
isSend + Sync
.Mutex<T>
isSync
ifT
isSend
. Even though theMutex
allows mutation ofT
, it synchronizes access, making it safe to share&Mutex<T>
across threads. Access to the innerT
is controlled via the lock.RwLock<T>
isSync
ifT
isSend + Sync
(for readers) andT
isSend
(for writers).- 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.Rc<T>
: Non-atomic reference counting makes sharing&Rc<T>
unsafe.- Raw pointers (
*const T
,*mut T
): NotSync
by default.
- Technically,
The compiler uses these traits implicitly when checking thread-related operations:
- The closure passed to
std::thread::spawn
must beSend
because it might be moved to a new thread. Any captured variables must also beSend
. - Data shared using
Arc<T>
requiresT: Send + Sync
because multiple threads might access it concurrently via immutable references derived from theArc
. - Attempting to use a non-
Send
type across threads (e.g., putting anRc<T>
inside anArc
and sending it to another thread) will result in a compile-time error. - Attempting to share a non-
Sync
type (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.