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.

04. Unsafe Rust, Layout Control & Systems Internals7 min read

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.

Deallocation RuleSafety Critical
Layout Invariant

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:

Layout(S,A) is Valid  ⟺  (A is a power of 2)∧(S+Padding≤isize::MAX)\text{Layout}(S, A) \text{ is Valid} \iff (A \text{ is a power of 2}) \quad \land \quad (S + \text{Padding} \le \text{isize::MAX})
Architecture Diagram
Rendering diagram...

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
use 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); } }
The Deallocation Layout Rule
Passing an incorrect 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use 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:

Concept check

What happens if you pass a Layout with a different size to dealloc() than was used during alloc()?

Concept check

What mathematical requirement must the alignment parameter of std::alloc::Layout satisfy?