Course contentsShow
← Rust
Lesson 162 of 1,9102. The Ownership ModelFree lesson

Custom Smart Pointer Logic

Implement a custom garbage-collected-like structure using manual reference tracking.

In this lesson, you will learn how to manually manage the lifecycle of data by creating a custom "reference-counting" structure that tracks how many pointers share a single piece of data.

Why Manual Reference Tracking?

In Rust, the compiler usually handles memory cleanup automatically via the Drop trait. However, sometimes you need to build custom structures—like a basic garbage-collected object—where you manually track how many parts of your program are "looking" at a piece of data. When that count hits zero, you know it is safe to deallocate.

Building a Simple Tracker

To implement this, we need to wrap our data in a struct that contains both the value and a counter. We use a raw pointer to point to the heap-allocated memory, and an integer to store the number of active references.

struct MyRef<T> {
    ptr: *mut (T, usize), // Stores the value and the count
}

impl<T> MyRef<T> {
    fn new(value: T) -> Self {
        let boxed = Box::new((value, 1));
        Self { ptr: Box::into_raw(boxed) }
    }

    fn clone(&self) -> Self {
        unsafe { (*self.ptr).1 += 1; }
        Self { ptr: self.ptr }
    }
}

Managing the Lifecycle

The core logic here is inside the Drop implementation. When a MyRef goes out of scope, it doesn't just destroy the data; it checks the counter first. If the count is greater than one, it simply decrements it. Only when the count reaches zero do we perform the actual cleanup:

impl<T> Drop for MyRef<T> {
    fn drop(&mut self) {
        unsafe {
            (*self.ptr).1 -= 1;
            if (*self.ptr).1 == 0 {
                // Manually reclaim memory
                let _ = Box::from_raw(self.ptr);
            }
        }
    }
}

In this pattern, we are manually upholding the "single owner" invariant by ensuring the memory is only freed once. We act as the "garbage collector" by checking our manual counter before letting the memory be reclaimed by Rust's Box deallocator. This demonstrates how you can exert low-level control over memory duration while maintaining a safe interface for the rest of your program.

Key Takeaway: Custom smart pointers allow you to override default drop behavior, enabling manual resource management by tracking usage counts at the cost of requiring unsafe blocks to manipulate raw pointers.