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
Guardstruct should hold the raw pointer and the deallocation function. - Automatic Deallocation: The
Droptrait implementation must call the provided deallocation function when theGuardis dropped. - Dereferencing: The
*operator should work onGuardinstances, 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
Guardshould be generic over the typeTit 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
Guardis null? The deallocation function should not be called in this case. - Double Free: Ensure that the deallocation function is only called once for each
Guardinstance. - 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
Guardstruct must be generic over the typeT. - The deallocation function must have the signature
fn(ptr: *mut T). - The
Guardmust implement theDroptrait. - The
Guardmust implement theDereftrait 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::Derefto implement dereferencing. - The
Droptrait 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
libccrate is used formallocandfreein the examples. You may need to add it to yourCargo.toml:[dependencies] libc = "0.2"