Skip to content

Chapter 7 of 8

Shared Ownership Across Threads

When multiple parts of a program legitimately need to own the same data, Rust offers reference-counted smart pointers. Rc<T> is the single-threaded choice: cloning an Rc<T> increments a reference count, dropping one decrements it, and the inner value is freed when the count reaches zero. Because the count is updated without atomic operations, Rc<T> is not Send or Sync, but it is the fastest shared-ownership pointer available outside of multithreading.

When ownership must be shared between threads, Arc<T> takes its place. The API is identical to Rc, but the count is updated with atomic operations, making it slightly slower but safe to send across thread boundaries. Both Rc and Arc support non-owning companions through Weak<T>, a reference counted alongside its strong counterpart but one that does not keep the value alive. Weak references cannot directly access the inner value; instead, upgrade() returns an Option<Rc<T>> (or Arc<T>), where None indicates that the value has already been dropped. This pattern is essential for breaking cycles: if two Rcs pointed at each other, neither would ever reach a count of zero. By making one direction Weak, the cycle is broken and memory can be reclaimed.

The compiler's Send and Sync marker traits are how Rust propagates thread-safety information through the type system. A type that implements Send can have its ownership transferred to another thread, while a type that implements Sync can be shared between threads through an &T. Both are auto-implemented for types whose components are all Send or Sync, and either can be implemented manually with unsafe when the programmer takes responsibility for upholding the guarantees. These markers prevent data races in safe Rust: a &mut T can only be Send if T: Send, and shared access through &T requires T: Sync. The intentional escape hatch from compile-time enforcement — when, for example, you need a graph data structure that references its own nodes — is the Rc<RefCell<T>> pattern, which admittedly can produce reference cycles and the memory leaks that the borrow checker alone cannot prevent.

All chapters
  1. 1Ownership Rules and Memory
  2. 2Moves, Copy, and Clone
  3. 3Borrowing and the Borrow Checker
  4. 4Lifetimes, Variance, and PhantomData
  5. 5Slices, Strings, and Deref Coercion
  6. 6Interior Mutability and Runtime Enforcement
  7. 7Shared Ownership Across Threads
  8. 8Patterns, Iteration, and Unsized Types

Drill it

Reading is not remembering. These come from the Rust Ownership And Borrowing Explained deck:

Q

What are the three ownership rules in Rust?

Each value in Rust has an owner. There can only be one owner at a time. When the owner goes out of scope, the value is dropped.

Q

What happens to a value when its owner goes out of scope in Rust?

It is dropped — its drop function runs and its memory is freed.

Q

Which standard library function is called when a value goes out of scope?

drop, from the Drop trait, is called automatically at the end of the variable's scope.

Q

What is a "move" in Rust?

A transfer of ownership: assigning a value to another variable or passing it to a function moves the value, invalidating the source binding.