Hone logo
Hone
Problems

Managing Memory with Raw Pointers in Rust

Rust's ownership system is a powerful tool for memory safety, but sometimes you need more direct control over memory. This challenge focuses on understanding and utilizing raw pointers in Rust, allowing you to interact with memory locations directly. Successfully completing this challenge demonstrates a deeper understanding of Rust's memory model and its potential for low-level programming.

Problem Description

You are tasked with implementing a simple memory manager using raw pointers. The memory manager should allow you to allocate a block of memory, write data to it, and deallocate it. You must use raw pointers (*mut T and *const T) for all memory operations. Do not use Box, Vec, or any other smart pointer types. The goal is to understand the manual memory management that raw pointers provide.

Specifically, you need to implement the following functions:

  1. allocate(size: usize) -> *mut u8: Allocates a block of size bytes of memory and returns a raw mutable pointer to the beginning of the allocated block. If allocation fails (e.g., due to insufficient memory), return a null pointer (std::ptr::null_mut()).
  2. deallocate(ptr: *mut u8, size: usize): Deallocates the memory block pointed to by ptr, which was previously allocated with allocate. The size parameter is crucial; it tells the deallocator how much memory to release. If ptr is a null pointer or the size doesn't match the originally allocated size, the behavior is undefined (you don't need to explicitly handle these errors, but be aware of them).
  3. write_data(ptr: *mut u8, data: &[u8]): Writes the contents of the data slice to the memory location pointed to by ptr. If the slice is larger than the allocated memory block, the behavior is undefined.

You will be provided with a simple allocator function unsafe_allocate and unsafe_deallocate for demonstration purposes. These functions are marked unsafe because they bypass Rust's safety guarantees. Your task is to build upon these to create the memory manager.

Examples

Example 1:

Input:
allocate(10) -> ptr1
write_data(ptr1, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
deallocate(ptr1, 10)
Output:
ptr1: *mut u8 (a valid memory address)
(write_data has no return value)
(deallocate has no return value)
Explanation:
10 bytes of memory are allocated, the slice is written to that memory, and then the memory is deallocated.

Example 2:

Input:
allocate(5) -> ptr2
write_data(ptr2, &[1, 2, 3])
deallocate(ptr2, 5)
Output:
ptr2: *mut u8 (a valid memory address)
(write_data has no return value)
(deallocate has no return value)
Explanation:
5 bytes of memory are allocated, the slice is written to that memory, and then the memory is deallocated.  Note that only 3 bytes of the slice are actually written to the allocated memory.

Example 3: (Edge Case)

Input:
allocate(0) -> ptr3
Output:
ptr3: *mut u8 (std::ptr::null_mut())
Explanation:
Attempting to allocate 0 bytes results in a null pointer being returned.

Constraints

  • size in allocate and deallocate will be a usize.
  • data in write_data will be a slice of u8 (&[u8]).
  • The unsafe_allocate and unsafe_deallocate functions are provided and should not be modified.
  • You must use raw pointers (*mut u8 and *const u8) exclusively for memory management.
  • Error handling beyond returning std::ptr::null_mut() on allocation failure is not required.
  • The provided allocator is a simplified version and may not be suitable for production use.

Notes

  • This challenge is inherently unsafe due to the use of raw pointers. Be extremely careful to avoid memory leaks, dangling pointers, and other memory-related errors.
  • The unsafe_allocate and unsafe_deallocate functions are placeholders and assume a simple allocation strategy. You don't need to implement your own allocator.
  • Consider the potential for undefined behavior if you write beyond the allocated memory or deallocate memory incorrectly.
  • This exercise is designed to illustrate the low-level nature of memory management and the importance of Rust's safety features.
use std::ptr;

// Placeholder allocator - DO NOT MODIFY
unsafe fn unsafe_allocate(size: usize) -> *mut u8 {
    let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
    std::alloc::alloc(layout)
}

// Placeholder deallocator - DO NOT MODIFY
unsafe fn unsafe_deallocate(ptr: *mut u8, size: usize) {
    let layout = std::alloc::Layout::from_size_align(size, 1).unwrap();
    std::alloc::dealloc(ptr, layout);
}

struct MemoryManager {}

impl MemoryManager {
    fn new() -> Self {
        MemoryManager {}
    }

    fn allocate(&self, size: usize) -> *mut u8 {
        unsafe {
            unsafe_allocate(size)
        }
    }

    fn deallocate(&self, ptr: *mut u8, size: usize) {
        unsafe {
            unsafe_deallocate(ptr, size)
        }
    }

    fn write_data(&self, ptr: *mut u8, data: &[u8]) {
        unsafe {
            let len = data.len();
            for i in 0..len {
                ptr::write_volatile(ptr.add(i), data[i]);
            }
        }
    }
}
Loading editor...
rust