Hone logo
Hone
Problems

Implement a Custom Reference-Counted Pointer in Rust

The standard library's Rc (Reference Counted) type in Rust allows multiple owners of a value, ensuring it's deallocated only when no references to it remain. This is crucial for scenarios where data needs to be shared immutably across different parts of your program. This challenge asks you to implement your own version of Rc to deepen your understanding of Rust's ownership, borrowing, and memory management.

Problem Description

Your task is to implement a custom reference-counted smart pointer, let's call it MyRc<T>. This type should behave similarly to std::rc::Rc<T>, enabling shared ownership of a value T.

Key Requirements:

  1. Shared Ownership: Multiple MyRc<T> instances can point to the same heap-allocated value.
  2. Reference Counting: A counter must track the number of active MyRc<T> references. The value should be deallocated when the count drops to zero.
  3. Immutability: MyRc<T> should provide immutable access to the contained value T.
  4. Cloning: The clone() method should increment the reference count and return a new MyRc<T> pointing to the same data.
  5. Dropping: When a MyRc<T> goes out of scope, its reference count should be decremented. If the count reaches zero, the contained value should be deallocated.
  6. Dereferencing: MyRc<T> should implement Deref to allow accessing the inner T using the * operator.

Expected Behavior:

  • Creating a MyRc<T> should allocate the value T on the heap along with its reference count.
  • Cloning a MyRc<T> should create a new MyRc<T> that shares ownership and increments the counter.
  • When a MyRc<T> is dropped, the counter should be decremented.
  • If the counter reaches zero upon dropping, the memory for both the value and the counter should be freed.
  • The contained value should be accessible via dereferencing.

Important Considerations:

  • This implementation does not need to be thread-safe (unlike std::sync::Arc).
  • You will need to manage the heap allocation yourself.
  • Consider how to handle the deallocation of both the reference count and the inner value.

Examples

Example 1: Basic Usage

Input:
let x = MyRc::new(5);
let y = MyRc::clone(&x);
let z = MyRc::clone(&x);

// Simulate x going out of scope
// Simulate y going out of scope
// Simulate z going out of scope

Output: The value 5 will be allocated. The reference count starts at 1. y is created, pointing to the same 5. The count becomes 2. z is created, pointing to the same 5. The count becomes 3. When x goes out of scope, the count becomes 2. When y goes out of scope, the count becomes 1. When z goes out of scope, the count becomes 0, and the value 5 is deallocated.

Example 2: Dereferencing

Input:
let data = MyRc::new(String::from("hello"));
let borrowed_data: &String = &data; // Implicit deref coercion
println!("{}", borrowed_data);

let another_ref = MyRc::clone(&data);
println!("{}", *another_ref); // Explicit dereference

Output:

hello
hello

Explanation: The MyRc::new(String::from("hello")) creates a MyRc holding the string. The first println! demonstrates that MyRc can be implicitly dereferenced to a &String for method calls and printing. The second println! shows explicit dereferencing using *another_ref to get a String, which then prints.

Example 3: Drop Behavior

Input:
{
    let _data1 = MyRc::new(vec![1, 2, 3]);
    let _data2 = MyRc::clone(&_data1);
    // _data1 and _data2 go out of scope here.
    // The reference count should be decremented for both.
    // The vector will only be deallocated when the count reaches zero.
}

Output: The vec![1, 2, 3] is allocated. The count becomes 1. _data2 is cloned, count becomes 2. When _data1 goes out of scope, count becomes 1. When _data2 goes out of scope, count becomes 0, and the vector is deallocated.

Constraints

  • The inner value T must be Sized.
  • You cannot use std::rc::Rc or std::sync::Arc in your implementation of MyRc.
  • You are free to use other standard library features like Box, clone, Drop, and basic memory management primitives.
  • Performance: While not strictly benchmarked, the implementation should be reasonably efficient, mirroring the behavior of std::rc::Rc in terms of overhead.

Notes

  • You'll likely need to store both the value T and the reference count together, possibly using Box.
  • The Drop trait will be essential for decrementing the reference count and deallocating memory.
  • Implementing Deref will be key to enabling the use of * and implicit dereferencing.
  • Think about how to manage the lifetime of the heap-allocated data and its associated reference count.
  • Consider what happens if T itself implements Drop. Your MyRc should correctly invoke T's drop implementation when the value is deallocated.
Loading editor...
rust