Hone logo
Hone
Problems

Custom Smart Pointer: Resource Guard with Guard

Smart pointers are essential tools in Rust for managing memory and resources safely. This challenge asks you to implement a custom smart pointer called Guard that wraps a raw pointer and ensures the resource it points to is automatically deallocated when the Guard goes out of scope. This is a fundamental concept in resource management and a great exercise in understanding Rust's ownership and borrowing system.

Problem Description

You need to create a Guard struct that encapsulates a raw pointer (*mut T) and a deallocation function. The Guard should implement the Drop trait, ensuring that the deallocation function is called when the Guard instance is dropped (goes out of scope). The Guard should provide a way to access the underlying data via dereferencing (*Guard). The deallocation function should be provided at construction time, allowing for flexible resource management (e.g., freeing memory allocated with malloc, closing a file handle, etc.).

Key Requirements:

  • Encapsulation: The Guard struct should hold the raw pointer and the deallocation function.
  • Automatic Deallocation: The Drop trait implementation must call the provided deallocation function when the Guard is dropped.
  • Dereferencing: The * operator should work on Guard instances, allowing access to the underlying data.
  • Safety: The implementation must be memory-safe and avoid undefined behavior. This includes ensuring the pointer is valid during dereferencing.
  • Genericity: The Guard should be generic over the type T it manages.

Expected Behavior:

When a Guard instance is created, it should take ownership of the raw pointer and store the deallocation function. When the Guard goes out of scope, the Drop implementation should call the deallocation function with the raw pointer as an argument. Dereferencing the Guard should return a reference (&T) to the data pointed to by the raw pointer.

Edge Cases to Consider:

  • Null Pointer: What should happen if the raw pointer passed to the Guard is null? The deallocation function should not be called in this case.
  • Double Free: Ensure that the deallocation function is only called once for each Guard instance.
  • Invalid Pointer: While Rust's safety features help prevent this, consider how your implementation handles potentially invalid pointers (e.g., dangling pointers). The dereference should panic if the pointer is null.

Examples

Example 1:

fn free_memory(ptr: *mut i32) {
    unsafe {
        libc::free(ptr);
    }
}

fn main() {
    let ptr = unsafe { libc::malloc(4) as *mut i32 };
    if ptr.is_null() {
        panic!("Memory allocation failed!");
    }

    let guard = Guard::new(ptr, free_memory);
    unsafe { *guard = 10; }

    // guard goes out of scope here, free_memory is called
}

Output: (No visible output, but memory is freed) Explanation: A raw pointer is allocated, a Guard is created wrapping it, the value 10 is written to the memory location, and then the Guard goes out of scope, triggering the free_memory function to deallocate the memory.

Example 2:

fn main() {
    let ptr: *mut i32 = std::ptr::null_mut();
    let guard = Guard::new(ptr, |p| {}); // Dummy deallocation function

    // guard goes out of scope here, deallocation function is NOT called
}

Output: (No visible output, and no memory is freed) Explanation: A null pointer is passed to the Guard. The Drop implementation correctly avoids calling the deallocation function.

Example 3: (Illustrating Dereferencing)

fn free_memory(ptr: *mut i32) {
    unsafe {
        libc::free(ptr);
    }
}

fn main() {
    let ptr = unsafe { libc::malloc(4) as *mut i32 };
    if ptr.is_null() {
        panic!("Memory allocation failed!");
    }

    let guard = Guard::new(ptr, free_memory);
    unsafe {
        *guard = 42;
        println!("{}", *guard); // Accessing the value through dereferencing
    }
}

Output:

42

Explanation: The *guard expression dereferences the Guard instance, allowing access to the underlying i32 value.

Constraints

  • The Guard struct must be generic over the type T.
  • The deallocation function must have the signature fn(ptr: *mut T).
  • The Guard must implement the Drop trait.
  • The Guard must implement the Deref trait to allow dereferencing.
  • The code must be safe and avoid undefined behavior.
  • The solution should be reasonably efficient (avoid unnecessary allocations or copies).

Notes

  • Consider using std::ops::Deref to implement dereferencing.
  • The Drop trait is automatically implemented for structs.
  • Think carefully about how to handle null pointers safely.
  • Rust's safety features are your friend – leverage them to prevent common memory errors.
  • The libc crate is used for malloc and free in the examples. You may need to add it to your Cargo.toml: [dependencies] libc = "0.2"
Loading editor...
rust