Hone logo
Hone
Problems

Implementing a Custom Reference Counted Pointer (Rc) in Rust

This challenge asks you to implement a simplified version of Rust's Rc (Reference Counted) smart pointer. Rc allows multiple owners of a single piece of data, automatically managing the lifetime of the data by tracking the number of references. Implementing your own Rc will deepen your understanding of ownership, borrowing, and memory management in Rust.

Problem Description

You are to implement a custom Rc type named MyRc. This type should provide the core functionality of Rc: shared ownership of a value and automatic deallocation when the last reference is dropped. Your MyRc should:

  • Hold a pointer to a value: The MyRc should store a pointer to the data it manages.
  • Maintain a reference count: A counter should track the number of MyRc instances pointing to the same data.
  • Provide a way to access the underlying data: A get() method should allow access to the data, returning an Option<&T> (to handle the case where the Rc has been dropped and the data deallocated).
  • Handle deallocation: When the last MyRc instance pointing to the data is dropped, the data should be deallocated.
  • Cloneable: MyRc should implement the Clone trait. Cloning should increment the reference count.
  • No Interior Mutability: Your MyRc should not provide a way to mutate the underlying data. It's purely for shared, immutable access.

Key Requirements:

  • Use raw pointers and unsafe blocks where necessary to manage memory directly. This is crucial for demonstrating the underlying mechanisms of Rc.
  • Ensure memory safety. Avoid dangling pointers and double-frees.
  • The data should be allocated on the heap.
  • The get() method should return None if the Rc has been dropped and the data deallocated.

Examples

Example 1:

Input:
let data = 5;
let rc1 = MyRc::new(&data);
let rc2 = rc1.clone();

assert_eq!(rc1.get(), Some(&5));
assert_eq!(rc2.get(), Some(&5));

Output: rc1.get() and rc2.get() both return Some(&5) Explanation: Both rc1 and rc2 point to the same data (5), and the reference count is 2.

Example 2:

Input:
let data = "hello";
let rc1 = MyRc::new(&data);
let rc2 = rc1.clone();
drop(rc1);
assert_eq!(rc2.get(), Some(&"hello"));

Output: rc2.get() returns Some(&"hello") Explanation: rc1 is dropped, decrementing the reference count to 1. rc2 still points to the data.

Example 3: (Edge Case - Deallocation)

Input:
let data = 10;
let rc1 = MyRc::new(&data);
let rc2 = rc1.clone();
drop(rc1);
drop(rc2);
let rc3 = MyRc::new(&data); // Attempt to create a new Rc after deallocation
assert_eq!(rc3.get(), None);

Output: rc3.get() returns None Explanation: rc1 and rc2 are dropped, deallocating the data. rc3 attempts to create a new Rc pointing to deallocated memory, which should return None.

Constraints

  • Memory Allocation: The data must be allocated on the heap using std::alloc::alloc and deallocated using std::alloc::dealloc. Do not use Box.
  • Pointer Size: Assume a 64-bit architecture.
  • Reference Count Size: The reference count should be stored in a usize.
  • No External Crates: You are not allowed to use any external crates beyond the standard library.
  • Safety: The code must be memory-safe. Panics due to memory errors are unacceptable.
  • Performance: While not the primary focus, avoid unnecessary allocations or copies.

Notes

  • Start by defining the MyRc struct, which should contain a raw pointer to the data and a raw pointer to the reference count.
  • The new() function should allocate memory for the data and the reference count, initialize the reference count to 1, and return a MyRc instance.
  • The clone() method should increment the reference count.
  • The get() method should safely dereference the raw pointer to the data, returning an Option<&T>.
  • The drop() method should decrement the reference count. If the reference count reaches 0, it should deallocate the data and the reference count.
  • Remember to use unsafe blocks appropriately when working with raw pointers. Carefully consider the lifetime implications of your code.
  • Consider using a helper function to handle the deallocation logic to avoid code duplication.
  • Thoroughly test your implementation with various scenarios, including cloning, dropping, and attempting to access data after deallocation.
Loading editor...
rust