Project 9: Custom Smart Pointer (Understand Rc, Arc, RefCell)
Your own implementations of Rc (reference counting), Arc (atomic reference counting), and RefCell (runtime borrow checking)—understanding exactly how Rust’s smart pointers work under the hood.
Quick Reference
| Attribute | Value |
|---|---|
| Primary Language | Rust |
| Alternative Languages | C++ (unique_ptr, shared_ptr comparison) |
| Difficulty | Level 3: Advanced |
| Time Estimate | 2-3 weeks |
| Knowledge Area | Smart Pointers / Interior Mutability / Drop |
| Tooling | Rust standard library source code |
| Prerequisites | Projects 1-2 and 6 completed |
What You Will Build
Your own implementations of Rc (reference counting), Arc (atomic reference counting), and RefCell (runtime borrow checking)—understanding exactly how Rust’s smart pointers work under the hood.
Why It Matters
This project builds core skills that appear repeatedly in real-world systems and tooling.
Core Challenges
- Implementing reference counting correctly → maps to understanding Drop and cycles
- Making Arc thread-safe with atomics → maps to understanding why Rc isn’t Send
- Runtime borrow checking in RefCell → maps to understanding the borrowing rules
- Handling memory properly in Drop → maps to manual memory management in unsafe
Key Concepts
- Smart pointers: “The Rust Programming Language” Chapter 15 - Steve Klabnik
- Interior mutability: “Rust for Rustaceans” Chapter 8 - Jon Gjengset
- Drop trait: “Programming Rust, 2nd Edition” Chapter 13 - Jim Blandy
- Reference cycles: “The Rust Programming Language” Chapter 15 - Steve Klabnik
Real-World Outcome
// Your Rc in action (matches std::rc::Rc behavior)
fn main() {
let a = MyRc::new(5);
println!("Count after creating a: {}", MyRc::strong_count(&a)); // 1
let b = MyRc::clone(&a);
println!("Count after creating b: {}", MyRc::strong_count(&a)); // 2
{
let c = MyRc::clone(&a);
println!("Count after creating c: {}", MyRc::strong_count(&a)); // 3
}
println!("Count after c goes out of scope: {}", MyRc::strong_count(&a)); // 2
}
// Your RefCell in action
fn main() {
let cell = MyRefCell::new(vec![1, 2, 3]);
// Multiple immutable borrows OK
let borrow1 = cell.borrow();
let borrow2 = cell.borrow();
println!("{:?}", *borrow1);
drop(borrow1);
drop(borrow2);
// Mutable borrow after immutable borrows released
cell.borrow_mut().push(4);
// This would panic at runtime:
// let b = cell.borrow();
// let m = cell.borrow_mut(); // panic: already borrowed!
}
Implementation Guide
- Reproduce the simplest happy-path scenario.
- Build the smallest working version of the core feature.
- Add input validation and error handling.
- Add instrumentation/logging to confirm behavior.
- Refactor into clean modules with tests.
Milestones
- Milestone 1: Minimal working program that runs end-to-end.
- Milestone 2: Correct outputs for typical inputs.
- Milestone 3: Robust handling of edge cases.
- Milestone 4: Clean structure and documented usage.
Validation Checklist
- Output matches the real-world outcome example
- Handles invalid inputs safely
- Provides clear errors and exit codes
- Repeatable results across runs
References
- Main guide:
LEARN_RUST_DEEP_DIVE.md - “Programming Rust, 2nd Edition” by Jim Blandy