Rc and Reference Counting
Rc and Reference Counting
In Rust, ownership rules dictate that each value has a single owner, and when the owner goes out of scope, the value is dropped. But what if you need multiple parts of your program to share ownership of the same data? This is where Rc<T> (Reference Counted) comes in.
Rc<T> is a smart pointer that enables shared ownership of a value. It keeps track of how many references (or "owners") exist to a value, and only deallocates the value when the last reference is dropped. This is called reference counting.
Rc<T> is useful in scenarios like:
- Graph data structures where nodes may have multiple incoming edges
- Caching systems where multiple components need access to the same data
- Event systems where multiple listeners observe the same source
Important: Rc<T> is only for single-threaded scenarios. For multi-threaded code, use Arc<T> (Atomic Reference Counted) instead.
Key Operations
Rc::new(value)- Create a new reference-counted pointerRc::clone(&rc)- Create a new reference (increments count, doesn't deep clone)Rc::strong_count(&rc)- Get the current number of strong referencesRc::weak_count(&rc)- Get the current number of weak referencesRc::downgrade(&rc)- Create aWeak<T>reference that doesn't prevent deallocationweak.upgrade()- Try to convert aWeak<T>back toRc<T>(returnsOption)
Your Task
Implement the following types and functions to demonstrate Rc<T> patterns:
Part 1: Basic Rc Operations
create_shared<T>(value: T) -> Rc<T>- Create a new reference-counted valueclone_shared<T>(rc: &Rc<T>) -> Rc<T>- Clone a reference (increment count)get_strong_count<T>(rc: &Rc<T>) -> usize- Get the strong reference countget_value<T: Clone>(rc: &Rc<T>) -> T- Get a clone of the inner value
Part 2: Shared Data Structure
SharedBuffer- A buffer that can be shared across multiple owners:new(data: Vec<u8>) -> Rc<Self>- Create a new shared bufferlen(&self) -> usize- Get the buffer lengthget(&self, index: usize) -> Option<u8>- Get a byte at indexas_slice(&self) -> &[u8]- Get the buffer as a slice
Part 3: Weak References
create_weak<T>(rc: &Rc<T>) -> Weak<T>- Create a weak referenceupgrade_weak<T>(weak: &Weak<T>) -> Option<Rc<T>>- Try to upgrade a weak referenceget_weak_count<T>(rc: &Rc<T>) -> usize- Get the weak reference count
Part 4: Graph Node with Rc
Node<T>- A graph node that can have multiple parents sharing child nodes:new(value: T) -> Rc<Self>- Create a new nodevalue(&self) -> &T- Get reference to the valueadd_child(&mut self, child: Rc<Node<T>>)- Add a child nodechildren(&self) -> &[Rc<Node<T>>]- Get all children
Part 5: Observer Pattern
-
Observable<T>- A value with weak observers:new(value: T) -> Self- Create observable with initial valueget(&self) -> &T- Get reference to valueset(&mut self, value: T)- Set new valuesubscribe(&mut self, observer: Rc<dyn Observer<T>>)- Add an observer (stored as Weak)notify(&self)- Notify all live observers
-
Observer<T>trait:on_update(&self, value: &T)- Called when observable changes
Examples
use rc_reference_counting::*;
use std::rc::Rc;
// Basic Rc operations
let shared = create_shared(42);
assert_eq!(get_strong_count(&shared), 1);
let cloned = clone_shared(&shared);
assert_eq!(get_strong_count(&shared), 2);
assert_eq!(get_value(&shared), 42);
// Shared buffer
let buffer = SharedBuffer::new(vec![1, 2, 3, 4, 5]);
assert_eq!(buffer.len(), 5);
assert_eq!(buffer.get(2), Some(3));
// Weak references
let strong = create_shared(String::from("hello"));
let weak = create_weak(&strong);
assert_eq!(get_weak_count(&strong), 1);
if let Some(upgraded) = upgrade_weak(&weak) {
assert_eq!(*upgraded, "hello");
}
drop(strong);
// Value was deallocated
assert!(upgrade_weak(&weak).is_none());Hints
Click here for hints
- Use
Rc::new()to wrap values for shared ownership Rc::clone(&rc)is cheap - it only increments a counter, doesn't copy dataRc::downgrade()creates aWeak<T>that doesn't count toward keeping the value aliveWeak::upgrade()returnsOption<Rc<T>>-Noneif the value was already dropped- For
Nodewith mutable children, you might needRefCellinside the node (interior mutability) - Weak references are perfect for observer patterns to avoid memory leaks from circular references
- Remember:
Rc<T>only works in single-threaded code!
Run to execute your code and see test results here.