Shared Ownership Pattern
While Rust defaults to unique single-owner trees (Box<T>, Vec<T>, String), many systems architectures require multiple independent components to share ownership of the same configuration, immutable asset buffer, or connection pool until the last active holder finishes. Rust models shared ownership explicitly via Rc<T> (single-threaded) and Arc<T> (thread-safe atomic reference counting).
Shared Immutable Slice (`Arc<[u8]>` / `Arc<str>` — Single Heap Allocation!):
Worker Thread 1: `Arc::clone(&config)` ---+
Worker Thread 2: `Arc::clone(&config)` ---+---> [ strong_count | weak_count | inline data... ]
Worker Thread 3: `Arc::clone(&config)` ---+ (Zero deep copies! Freed when last worker drops!)
Four High-Value Shared Ownership Idioms
| Idiom | Type Signature | Why It Beats Naive Alternatives |
|---|---|---|
| 1. Compact Shared Strings / Slices | Arc<str> / Arc<[T]> |
Unlike Arc<String> or Arc<Vec<T>> (which have two pointer indirections: Arc $\to$ String header $\to$ heap bytes), Arc<str> and Arc<[T]> store the reference counts and string/slice bytes in one single contiguous heap allocation! |
| 2. Copy-on-Write Shared Mutation | Arc::make_mut(&mut arc_val) |
If strong_count == 1, mutates the inner T in place with zero allocation; if strong_count > 1, clones T into a fresh Arc<T> automatically! |
| 3. Lock-Free Config Hot-Reloading (RCU) | Mutex<Arc<Config>> (or ArcSwap) |
Readers lock the mutex for ~5ns only to Arc::clone(¤t_cfg) and release the lock immediately; writers build a new Arc<Config> and swap the pointer! |
| 4. Non-Owning Observer / Cache Handles | Weak<T> (Arc::downgrade) |
Allows a cache or event bus to reference a live object without keeping it alive forever; .upgrade() returns Some(Arc<T>) if still alive or None if dropped. |
Demonstrating Copy-on-Write (Arc::make_mut) and RCU Config Snapshots
Arc::make_mut (and Rc::make_mut) powers persistent data structures and copy-on-write state snapshots: multiple readers can share an Arc<State> cheaply, and when one owner needs to modify its snapshot, Arc::make_mut(&mut snapshot) only clones the data if another owner is still holding a reference!
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RoutingTable {
pub version: u32,
pub routes: Vec<(&'static str, &'static str)>,
}
fn main() {
// 1. `Arc<str>` allocates the ref-counts and string bytes in ONE contiguous block:
let shared_tenant: Arc<str> = Arc::from("tenant_enterprise_01");
let worker_copy = Arc::clone(&shared_tenant);
assert_eq!(&*worker_copy, "tenant_enterprise_01");
// 2. Copy-on-Write mutation via `Arc::make_mut`:
let mut table_v1 = Arc::new(RoutingTable {
version: 1,
routes: vec![("/health", "local")],
});
// Because `strong_count == 1`, `Arc::make_mut` mutates `table_v1` IN PLACE with 0 clones!
let ptr_before = Arc::as_ptr(&table_v1);
Arc::make_mut(&mut table_v1).routes.push(("/v1/api", "backend_a"));
assert_eq!(Arc::as_ptr(&table_v1), ptr_before);
// Now share a snapshot with a reader (`strong_count == 2`):
let reader_snapshot = Arc::clone(&table_v1);
// Mutating `table_v1` via `Arc::make_mut` while `reader_snapshot` is alive
// automatically clones `table_v1` into a new allocation, leaving `reader_snapshot` untouched!
let mutable_table = Arc::make_mut(&mut table_v1);
mutable_table.version = 2;
mutable_table.routes.push(("/v2/api", "backend_b"));
assert_eq!(reader_snapshot.version, 1);
assert_eq!(reader_snapshot.routes.len(), 2);
assert_eq!(table_v1.version, 2);
assert_eq!(table_v1.routes.len(), 3);
println!(
"Reader snapshot stayed at v{} ({} routes), writer advanced to v{} ({} routes)",
reader_snapshot.version,
reader_snapshot.routes.len(),
table_v1.version,
table_v1.routes.len()
);
}
Reader snapshot stayed at v1 (2 routes), writer advanced to v2 (3 routes)
Encapsulating Arc Inside Handle Types
When designing a thread-safe client (such as a DatabasePool or HttpClient) that is meant to be cloned and passed across threads/tasks, wrap the Arc<PoolInner> inside #[derive(Clone)] pub struct DatabasePool { inner: Arc<PoolInner> } rather than forcing callers to write Arc<DatabasePool> in every function signature.