On this page

Send and Sync

Send and Sync are the cornerstones of Rust concurrency safety—Send allows transferring ownership of a type across threads, while Sync allows sharing references to a type across threads. They are auto traits (inferred by the compiler): if all fields of a type are Send, the type is automatically Send. PhantomData is used to manually suppress or restore this inference—crucial in unsafe code and FFI.

What are Send and Sync?

Send and Sync are the core of Rust's concurrency safety—they are compile-time marker traits that indicate whether a type can be used across threads.

Send: Ownership of this type can be transferred between threads. If all fields of a type are Send, it is Send—the compiler infers this automatically. Rc<T> is not Send—its reference counting operations are not atomic, and moving it into another thread could lead to incorrect counts.

Sync: This type can be accessed between threads via shared references (&T). Formally: T: Sync if and only if &T: Send. RefCell<T> is not Sync—its runtime borrow checking does not guarantee thread safety. Mutex<T> is Sync (when T: Send)—Mutex protects its interior with locks, allowing safe access via &Mutex<T> from multiple threads.

How the Compiler Infers Them

The compiler checks each field of a type: if all fields are Send, the type is automatically Send. If all fields are Sync, the type is automatically Sync. A type loses these properties only when it introduces raw pointers (*const T, *mut T) or non-thread-safe primitives (Rc, RefCell, Cell, UnsafeCell).

fn assert_send<T: Send>() {}
assert_send::<i32>();                // OK
assert_send::<Arc<String>>();        // OK (Arc synchronizes via atomic operations)
// assert_send::<Rc<String>>();      // COMPILE ERROR
// assert_send::<*const i32>();      // COMPILE ERROR — raw pointers lack ownership semantics

fn assert_sync<T: Sync>() {}
assert_sync::<Mutex<i32>>();         // OK (Mutex ensures internal synchronization)
// assert_sync::<RefCell<i32>>();    // COMPILE ERROR

Controlling Inference Manually with PhantomData

When implementing custom containers using raw pointers, raw pointers do not carry Send/Sync semantics—but your container might logically guarantee them:

use std::marker::PhantomData;
use std::rc::Rc;

struct MyNonSend {
    data: i32,
    _not_send: PhantomData<Rc<()>>,  // Marks: this type is not Send (inherits Rc's property)
}
// Although MyNonSend only contains i32 internally (which is Send), PhantomData<Rc<()>> makes it !Send

PhantomData occupies no memory (it is a ZST) and serves only as a type-level marker. Authors of unsafe code use PhantomData to communicate to the compiler "this type should/should not be Send/Sync"—and the compiler then performs correctness checks for callers.

Why Not "All Types Are Send by Default"

The standard library's types are carefully designed: Rc<T> is inherently not Send, and Mutex<T> is inherently Sync. Any third-party library introducing interior mutability must carefully consider the propagation of Send/Sync. If a type accidentally becomes Send when it is not actually thread-safe, it would create a backdoor for data races. This design of "automatic inference + human intervention" allows Rust to balance performance (zero overhead) and safety (compile-time guarantees).

References

  • Rustonomicon: Send and Sync
  • Rust Reference: auto traits

Keywords: Send, Sync, auto trait, PhantomData, thread safety, marker type, Rc, RefCell