DEV Community

์ด๊ด€ํ˜ธ(Gwanho LEE)
์ด๊ด€ํ˜ธ(Gwanho LEE)

Posted on • Edited on

Implementing a Slab Allocator in Rust

๐Ÿง  Implementing a Slab Allocator in Rust

A slab allocator is a memory management technique used in system-level programming where memory is pre-allocated in fixed-size chunks, often for performance and predictability. It's commonly used in kernels, embedded systems, and real-time environments where dynamic memory allocation (like Box, Vec, or Rc) may be too costly or even unavailable.

In this blog post, we'll explore how to implement a simple slab allocator in Rust using safe abstractions like Option, RefCell, and arrays.


๐Ÿงฑ Motivation

In embedded or OS-level Rust, we often cannot use the heap, or we want more control over allocations. A slab allocator helps by:

  • Avoiding fragmentation
  • Offering constant-time allocation/deallocation
  • Reusing fixed-size memory blocks

๐Ÿ› ๏ธ Core Idea

We simulate a heap by using a fixed-size array of Option<T>, and manage free slots with a Vec<usize> called free_list.

use std::cell::RefCell;

pub struct Slab<T, const N: usize> {
    data: RefCell<[Option<T>; N]>,
    free_list: RefCell<Vec<usize>>,
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ฆ RefCell

We use RefCell so that we can mutate the contents safely even if the Slab is held behind an immutable reference. This is a form of interior mutability.


๐Ÿ”ง Constructor

We initialize the free_list with all possible indices:

impl<T, const N: usize> Slab<T, N> {
    pub fn new() -> Self {
        Self {
            data: RefCell::new(array_init::array_init(|_| None)),
            free_list: RefCell::new((0..N).rev().collect()),
        }
    }
Enter fullscreen mode Exit fullscreen mode

We use array_init crate to initialize [Option<T>; N] (because default array initialization for non-Copy types is not trivial).


โž• Allocate a Slot

    pub fn insert(&self, value: T) -> Option<usize> {
        let mut free = self.free_list.borrow_mut();
        if let Some(index) = free.pop() {
            self.data.borrow_mut()[index] = Some(value);
            Some(index)
        } else {
            None // Slab full
        }
    }
Enter fullscreen mode Exit fullscreen mode

โž– Remove a Slot

    pub fn remove(&self, index: usize) -> Option<T> {
        let mut data = self.data.borrow_mut();
        if index >= N {
            return None;
        }
        if data[index].is_some() {
            let value = data[index].take();
            self.free_list.borrow_mut().push(index);
            value
        } else {
            None
        }
    }
Enter fullscreen mode Exit fullscreen mode

๐Ÿ“ฆ Access Slot

    pub fn get(&self, index: usize) -> Option<T> where T: Clone {
        if index >= N {
            return None;
        }
        self.data.borrow()[index].clone()
    }
}
Enter fullscreen mode Exit fullscreen mode

โœ… Benefits

  • Safe: No unsafe used
  • Fast: Constant-time allocation
  • Predictable: Useful in embedded/real-time

๐Ÿ’ผ Real-World Use Cases

  • Kernel object allocation (e.g., file descriptors, processes)
  • Embedded sensor buffers
  • Network packet storage
  • Real-time schedulers

๐Ÿš€ Final Thoughts

This slab allocator is intentionally simple, but demonstrates real systems principles:

  • Memory reuse
  • Avoiding heap allocations
  • Interior mutability for shared state

Thanks for reading! ๐Ÿ™Œ

Want more like this? Follow me or reach out to talk Rust, blockchain, and low-level systems programming. ๐Ÿ’ฌ

Top comments (0)