Skip to content

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 Arc lock-free and concluding that its atomics are free.
  • Treating Arc::strong_count as a stable synchronization decision.
  • Assuming the allocation is freed immediately when the last strong owner disappears, even if Weak handles 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_count for correctness?

References

  • Rust Standard Library
  • Rustonomicon