Smart Pointers, Interior Mutability (RefCell) & Deref Coercion

Master shared reference counting with Rc and Arc, atomic acquire-release fences, runtime borrow checking with RefCell, and Deref coercion.

03. Advanced Abstractions, Traits & Smart Pointers7 min read

While the standard ownership model enforces unique owners and compile-time borrow rules, complex data structures (like graphs, observer patterns, or shared caches) require multiple shared owners and interior mutability.

Rust provides standard Smart Pointers that wrap raw heap addresses with deterministic lifecycle tracking and runtime safety checks.

RefCell Borrow StateInterior Mutability
-1 vs >0

RefCell stores a signed counter: 0 means free, >0 tracks active readers, and -1 signals an exclusive writer.

1. Reference Counting: Rc vs Arc#

Rc<T> and Arc<T> enable shared ownership of heap allocations by maintaining an explicit reference counter:

Architecture Diagram
Rendering diagram...

The Architectural Difference#

Smart PointerConcurrency SafetyCounter Increment MechanismPerformance Profile
Rc<T>Single-threaded only (does not implement Send/Sync).Normal integer addition (count += 1).Maximum performance in local graphs.
Arc<T>Thread-safe across multi-core CPUs.Atomic CPU fetch-and-add (FETCH_ADD with Acquire-Release memory ordering).Incurs cross-core CPU cache-coherency bus traffic.
Avoid Arc in Single-Threaded Code
Atomic instructions force hardware CPU cores to synchronize their L1/L2 caches. Do not use Arc when data remains confined to a single thread; prefer Rc to avoid unnecessary cache line stalls.

2. Interior Mutability: RefCell<T>#

Normally, you cannot mutate a value behind an immutable reference &T. RefCell<T> relaxes this restriction by shifting borrow checking from compile time to runtime.

Hardware Representation of RefCell#

RefCell<T> wraps value TT alongside a signed integer borrow counter (Cell<isize>):

0 & \text{Unborrowed (Free)} \\ > 0 & \text{Active Immutable Borrows (Reader count)} \\ -1 & \text{Active Exclusive Mutable Borrow (Writer active)}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use std::cell::RefCell; fn main() { let data = RefCell::new(vec![10, 20, 30]); { // Increment borrow counter to 1 let mut writer = data.borrow_mut(); writer.push(40); // Counter drops back to 0 at scope exit } // Now safe to borrow immutably let reader = data.borrow(); println!("Data: {:?}", *reader); // DANGEROUS: Attempting concurrent &mut while borrowed immutably: // let mut bad_writer = data.borrow_mut(); // RUNTIME PANIC: AlreadyBorrowed! }

3. Deref Coercion#

Rust provides Deref Coercion to make working with smart pointers seamless. If type UU implements Deref<Target = T>, the compiler automatically coerces &U to &T when needed:

&SmartPointer<T>→Deref Coercion&T\&SmartPointer<T> \xrightarrow{\text{Deref Coercion}} \&T
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn display_text(slice: &str) { println!("{}", slice); } fn main() { // String implements Deref<Target = str> let my_string = String::from("Rust Systems"); // Box<T> implements Deref<Target = T> let boxed_string = Box::new(String::from("Systems Architecture")); // rustc applies deref coercion chains automatically: display_text(&my_string); // &String -> &str display_text(&boxed_string); // &Box<String> -> &String -> &str }

4. Concept Verification#

Test your understanding of smart pointers and interior mutability:

Concept check

How does RefCell enforce Rust's XOR aliasing rule without a compile-time check?

Concept check

Why can Rc<T> not be sent across threads in Rust?