TRT
Sign in
easyAdvancedconcept

Explain how `Send`, `Sync`, and `UnsafeCell`T`` work together in Rust—why is `RefCell`T`` `Send` but `!Sync`, why is `Rc`T`` both `!Send` and `!Sync`, and how.

Prove why `RefCell` is `Send + !Sync`, why `Rc` is `!Send + !Sync`, why `Mutex`T`` owns `T` inside the lock, and when `std::sync::Mutex` is faster than `tokio::sync::Mutex`.

TRT practice prompt — not a verified question from a named employer.

The Runtime Theory Team2 min read

A strong answer

Rust's concurrency safety rests on two auto-traits and four smart containers: (1) Send and Sync: A type T is Send if ownership of T can be transferred to another thread; T is Sync if a shared reference &T can be accessed from multiple threads simultaneously (i.e., T is Sync $\iff$ &T is Send!). (2) RcT vs `Arc`T: RcT increments its reference count with fast non-atomic CPU instructions (`cell.set(count + 1)`), so if two threads cloned an `Rc` simultaneously, they would race on the counter! Therefore `Rc`T is explicitly marked !Send and !Sync. **ArcT (Atomically Reference Counted)** uses hardware atomic instructions (`fetch_add(..., Ordering::Relaxed)`), so `Arc`T is Send + Sync (whenever T: Send + Sync). (3) Interior Mutability (UnsafeCellT):** To mutate data through a shared reference `&T` (such as inside an `Rc`T or ArcT), Rust moves the borrow-checker's Aliasing-XOR-Mutability check from compile time to runtime: **`RefCell`T tracks a non-atomic runtime borrow counter (borrow() / borrow_mut(), panicking if violated on 1 thread, hence !Sync), while MutexT`` guards T inside an OS/futex lock so the only way to get &mut T is by acquiring the MutexGuard via .lock()!

In safe Rust, every byte reachable through a shared reference &T is deeply immutable unless it lies inside an UnsafeCellT (the sole core-library building block of `Cell`, `RefCell`, `Mutex`, `RwLock`, and `Atomic*`); and no `!Send` type can ever cross a `thread::spawn` or multi-threaded `tokio::spawn` boundary. Because `std::thread::spawn` has the trait bound `F: FnOnce() -> T + Send + 'static`, any closure capturing an `Rc`T (which is !Send) or a &RefCellT`` (where RefCell is !Sync, so &RefCell is !Send) is rejected by the trait solver before machine code is generated.

Rc::clone costs ~1 ns (non-atomic add), while Arc::clone costs ~5–15 ns (atomic LOCK XADD cache-line operation)—and borrowing &T (rather than cloning ArcT``) inside helper functions avoids atomic contention completely! In production systems (Tokio shared state (Arc<Mutex<State>> or Arc<RwLock<State>>), std::sync::OnceLock / LazyLock, Actix-web single-threaded Rc workers, and servo DOM trees (Rc<RefCell<Node>> with Weak<Node> parent pointers).), engineers monitor these exact trade-offs to keep p99 latency predictable and prevent resource exhaustion under peak load.

What the interviewer is listening for

  • Traces how smart pointers (Rc/Arc), Interior Mutability (RefCell/Mutex), and Send/Sync thread safety operates in memory step by step rather than giving a vague textbook definition.
  • States the core correctness invariant and explains why edge cases do not violate it.
  • Quantifies time, space, and hardware costs (BoxT`` (Unique Heap Owner), Rc<RefCellT> (1 Thread)) with concrete numbers.
  • Identifies real-world failure modes (Holding a RefMut (let mut b = cell.borrow_mut();) alive in the same scope where you call a helper function that also calls cell.borrow()) and explains how production systems mitigate them.

Common mistakes

  • Treating smart pointers (Rc/Arc), Interior Mutability (RefCell/Mutex), and Send/Sync thread safety as a black box without explaining the underlying memory or state transitions.
  • Ignoring worst-case tail latency or memory overhead (Holding a std::sync::MutexGuard across an .await point in a multi-threaded tokio task).
  • Forgetting boundary conditions and error recovery (Using Arc<String> or Arc<VecT> when immutable shared data is never grown after creation).

This answer walks

Practice follow-ups

  1. 01In an async `tokio` service, why do the official Tokio docs recommend using **`std::sync::Mutex`** (NOT `tokio::sync::Mutex`!) for synchronous in-memory data structures like a `HashMap`, as long as.
  2. 02What is 'Mutex Poisoning' in Rust (`lock().unwrap()`), and why does `Mutex::lock()` return a `Result<MutexGuard`T`, PoisonError>`?

Not started

Sign in to save your learning progress.

Sign in to save