Thread Safety: Auto-Traits Send and Sync Compile-Time Verification
- Last updated
- Prerequisites:
- Rust ownership and borrowing
- Understanding of threads and shared state
- rust
- send
- sync
- auto-traits
- thread-safety
- concurrency
Read at your depth
The practical view
Send and Sync are marker traits (auto-traits) that the compiler derives automatically. Send: a type can be moved to another thread safely (ownership transfer). Sync: a type can be shared by reference across threads (&T is Send). Most types are Send + Sync. Exceptions: Rc<T> is neither (not thread-safe); RefCell<T> and Cell<T> are Send but not Sync (single-threaded interior mutability); *const T/*mut T raw pointers are not Send/Sync by default (unsafe). Send/Sync are enforced at compile time: you cannot move a !Send value to another thread or share a !Sync value. You can implement them manually with unsafe impl for types you control (with responsibility for correctness).
The same idea in other frameworks
N/A — Rust language feature.N/A — Rust feature.N/A — Rust feature.N/A — Rust feature.Legacy vs modern
Sharing Rc across threads (compile error) vs Arc
Rc's non-atomic refcount makes it !Send/!Sync — a compile error; Arc uses atomic refcounting and is shareable.
let rc = Rc::new(5);
thread::spawn(move || { let _ = *rc; }); // ❌ OLD: compile error — Rc is !Sendlet arc = Arc::new(5); // ✅ NEW: atomic refcount, Send + Sync
let h = thread::spawn(move || { let _ = *arc; }); // ✅ NEW: compilesInterview gotchas
Context
This tests the two auto-trait semantics — interviewers want the move-vs-share distinction and the precise relationship (Sync ⇔ &T is Send), not just 'thread-safe markers'.
The mechanical answer
Send means a value can be safely MOVED to another thread — ownership is transferred, and the receiver can use it exclusively. Sync means a value can be safely SHARED across threads — &T can be sent to another thread and read concurrently. The precise relationship: T: Sync is equivalent to &T: Send (if a shared reference to T can be sent, T is Sync). A type can be Send but not Sync (e.g. RefCell<T>: you can move it to another thread, but you cannot share &RefCell across threads because its borrow flag is non-atomic). A type can be neither (Rc). The guarantees are compile-time: thread::spawn requires the closure (including captured values) to be Send; sharing across threads requires the value to be Sync (or wrapped in a lock/Arc). These are the compile-time translation of 'no data races'.
Trap
The naive answer is 'Send and Sync are the same thing'. Send = move across threads; Sync = share across threads, and the &T: Send equivalence is the precise relationship. Another trap: assuming all standard types are Send+Sync — Rc, RefCell, raw pointers, and *const T are not, by design. Candidates who state the &T: Send equivalence show real understanding.
Context
This probes the auto-trait mechanics and the unsafe escape hatch — interviewers want the structural derivation and the responsibility contract of unsafe impl Send/Sync.
The mechanical answer
Send and Sync are auto-traits: the compiler synthesizes the impl automatically by checking every field of the type — a struct is Send if all its fields are Send, and Sync if all fields are Sync (with negative impls overriding for types like UnsafeCell, Rc, raw pointers). You can write unsafe impl Send/Sync for a type you define when the compiler cannot prove it but YOU guarantee it's actually safe — e.g. a wrapper around raw pointers that you know are only accessed from one thread at a time. This is an unsafe contract: if your guarantee is wrong, you introduce data races that the compiler cannot catch. Safe code can rely on Send/Sync bounds without any special action; the unsafe impls are the escape hatch for FFI wrappers, thread-local primitives, and lock-free structures.
Trap
The naive answer is 'unsafe impl Send makes my type thread-safe'. It only ASSERTS thread safety to the compiler; you are responsible for the actual safety — a wrong unsafe impl is a soundness hole (UB). Another trap: assuming the compiler derives Send/Sync only for simple structs — it's structural for all composed types (Vec<T>, Option<T>, Box<T> propagate the bounds of T). Candidates who describe the structural derivation and the unsafe-contract responsibility show real depth.