Manual Memory Management: std::alloc & Custom Global Allocators
Master direct heap allocation with std::alloc, Layout invariants, deallocation matching rules, and high-performance custom global allocators.
Standard collections like Vec<T> and Box<T> manage heap allocations automatically. However, when building custom data structures, arena memory pools, or deterministic embedded runtimes, systems engineers interact directly with std::alloc.
Managing raw memory requires absolute compliance with memory Layout invariants.
Calling dealloc requires passing the EXACT size and alignment Layout used during the original alloc call.
1. The Anatomy of a Layout#
Before requesting bytes from the system allocator, you must construct a valid std::alloc::Layout describing the memory block's size and alignment:
If alignment is not a power of two or size exceeds isize::MAX, layout construction fails, preventing invalid memory allocations.
2. Low-Level Allocation & Deallocation#
The std::alloc module exposes low-level C-style allocation routines:
123456789101112131415161718192021222324252627use std::alloc::{alloc, dealloc, handle_alloc_error, Layout}; fn main() { // 1. Define layout for an array of 4 u64 integers (32 bytes, 8-byte aligned) let layout = Layout::array::<u64>(4).expect("Valid array layout"); unsafe { // 2. Allocate raw heap memory: let raw_ptr = alloc(layout) as *mut u64; // Verify allocator returned a non-null address if raw_ptr.is_null() { handle_alloc_error(layout); } // 3. Initialize memory: for i in 0..4 { raw_ptr.add(i).write((i as u64) * 100); } // Read values back: println!("Value at index 2: {}", *raw_ptr.add(2)); // 200 // 4. CRITICAL: Deallocate with the EXACT SAME layout! dealloc(raw_ptr as *mut u8, layout); } }
Layout (e.g. wrong size or mismatched alignment) to dealloc is immediate Undefined Behavior. Memory allocators store free-lists indexed by bucket size; a layout mismatch corrupts the allocator's internal heap tables, triggering memory corruption bugs.3. Implementing a Custom #[global_allocator]#
Rust allows replacing the default operating system allocator with high-performance alternatives like mimalloc or jemalloc across the entire crate:
1234567891011121314151617181920use std::alloc::{GlobalAlloc, Layout, System}; // Simple counting allocator wrapper struct ProfilingAllocator; unsafe impl GlobalAlloc for ProfilingAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { // Intercept allocation requests for metrics/telemetry: System.alloc(layout) } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { // Intercept deallocation: System.dealloc(ptr, layout) } } // Register as the global allocator for the entire program: #[global_allocator] static GLOBAL: ProfilingAllocator = ProfilingAllocator;
4. Concept Verification#
Test your understanding of manual heap allocation and layout rules:
What happens if you pass a Layout with a different size to dealloc() than was used during alloc()?
What mathematical requirement must the alignment parameter of std::alloc::Layout satisfy?