Theme
Q040. How does Arc work internally, and what do clone and drop cost?
Short Answer
An Arc<T> points to one allocation containing T plus atomic strong and weak reference counts. Cloning an Arc performs an atomic increment of the strong count and copies the pointer-sized handle. Dropping one performs an atomic decrement; the transition to zero drops T, and the allocation is freed after the weak count also reaches zero. These operations are constant time but can be costly under contention because the count's cache line is shared.
Deep Dive
The strong count represents owners that may access T. Arc::downgrade creates a Weak<T>, which does not keep T alive. Weak::upgrade atomically checks whether a strong owner still exists and returns an Arc only if it does.
The exact layout and memory orderings are implementation details, so interview answers should focus on guarantees rather than claim one permanent representation. The important performance fact is that clone and drop require atomic read-modify-write operations. Frequent cloning across CPU cores can cause cache-line traffic even though no lock is taken.
Passing &Arc<T> or &T through an inner call can avoid unnecessary count changes. Do not contort ownership merely to eliminate every clone: measure when clone/drop traffic is actually on a hot path.
Internal Model
Conceptually, the allocation looks like:
text
Arc handle -> [ strong count | weak count | T ]The last strong drop establishes the synchronization needed to destroy T safely after prior users are gone. The allocation cannot be released while a Weak still points to its metadata. Standard library internals also maintain bookkeeping that makes the precise weak count more nuanced than this mental model.
Example
rust
use std::sync::Arc;
fn main() {
let first = Arc::new(String::from("order-book"));
let second = Arc::clone(&first);
let observer = Arc::downgrade(&first);
assert_eq!(Arc::strong_count(&first), 2);
drop(second);
assert_eq!(observer.upgrade().as_deref(), Some(&String::from("order-book")));
drop(first);
assert!(observer.upgrade().is_none());
}Common Mistakes
- Calling
Arclock-free and concluding that its atomics are free. - Treating
Arc::strong_countas a stable synchronization decision. - Assuming the allocation is freed immediately when the last strong owner disappears, even if
Weakhandles remain.
Follow-ups
- Why can atomic reference counting scale poorly under heavy clone/drop traffic?
- How does
Weak<T>break ownership cycles? - Why should code not use
strong_countfor correctness?
Related
- Q039. What does
Arc<T>provide, and when should you use it? - Q041. Why does
Arcnot automatically makeTthread-safe? - Q042. When should you use
Arc,Mutex,RwLock, atomics, or message passing?
References
- Rust Standard Library
- Rustonomicon