#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StackMapError {
MapFull,
}
#[derive(Debug)]
pub struct StackMap<K, V, const N: usize> {
entries: [Option<Entry<K, V>>; N],
size: usize,
}
#[derive(Debug, Clone, Default)]
pub struct Entry<K, V> {
key: K,
value: V,
deleted: bool,
}
impl<K, V, const N: usize> StackMap<K, V, N>
where
K: PartialEq,
{
pub fn new() -> Self {
Self {
entries: [const { None }; N],
size: 0,
}
}
pub fn insert_or_update(&mut self, key: K, value: V) -> Result<(), StackMapError> {
for i in 0..N {
if let Some(entry) = &mut self.entries[i] {
if !entry.deleted && entry.key == key {
entry.value = value;
return Ok(());
}
}
}
for i in 0..N {
match &mut self.entries[i] {
None => {
self.entries[i] = Some(Entry {
key,
value,
deleted: false,
});
self.size += 1;
return Ok(());
}
Some(entry) if entry.deleted => {
*entry = Entry {
key,
value,
deleted: false,
};
self.size += 1;
return Ok(());
}
_ => continue,
}
}
Err(StackMapError::MapFull)
}
pub fn get(&self, key: &K) -> Option<&V> {
for i in 0..N {
if let Some(entry) = &self.entries[i] {
if !entry.deleted && &entry.key == key {
return Some(&entry.value);
}
}
}
None
}
pub fn remove(&mut self, key: &K) -> bool {
for i in 0..N {
if let Some(entry) = &mut self.entries[i] {
if !entry.deleted && &entry.key == key {
entry.deleted = true;
self.size -= 1;
return true;
}
}
}
false
}
pub fn len(&self) -> usize {
self.size
}
pub fn is_empty(&self) -> bool {
self.size == 0
}
pub fn capacity(&self) -> usize {
N
}
pub fn clear(&mut self) {
for entry in self.entries.iter_mut() {
*entry = None;
}
self.size = 0;
}
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
self.entries.iter().filter_map(|entry| match entry {
Some(e) if !e.deleted => Some((&e.key, &e.value)),
_ => None,
})
}
}
impl<K, V, const N: usize> StackMap<K, V, N>
where
K: PartialEq,
V: Clone,
{
pub fn take(&mut self, key: &K) -> Option<V> {
for i in 0..N {
if let Some(entry) = &mut self.entries[i] {
if !entry.deleted && &entry.key == key {
entry.deleted = true;
self.size -= 1;
return Some(entry.value.clone());
}
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::atomic::{AtomicUsize, Ordering};
#[global_allocator]
static ALLOCATOR: AllocationTracker = AllocationTracker;
static ALLOCATION_COUNT: AtomicUsize = AtomicUsize::new(0);
static ALLOCATED_BYTES: AtomicUsize = AtomicUsize::new(0);
struct AllocationTracker;
unsafe impl GlobalAlloc for AllocationTracker {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
ALLOCATION_COUNT.fetch_add(1, Ordering::SeqCst);
ALLOCATED_BYTES.fetch_add(layout.size(), Ordering::SeqCst);
System.alloc(layout)
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
System.dealloc(ptr, layout);
}
}
fn reset_allocation_stats() {
ALLOCATION_COUNT.store(0, Ordering::SeqCst);
ALLOCATED_BYTES.store(0, Ordering::SeqCst);
}
fn get_allocation_count() -> usize {
ALLOCATION_COUNT.load(Ordering::SeqCst)
}
fn get_allocated_bytes() -> usize {
ALLOCATED_BYTES.load(Ordering::SeqCst)
}
#[test]
fn test_new_map_is_empty() {
let map = StackMap::<i32, String, 16>::new();
assert!(map.is_empty());
assert_eq!(map.len(), 0);
assert_eq!(map.capacity(), 16);
assert_eq!(map.get(&42), None);
}
#[test]
fn test_insert_and_get() {
let mut map = StackMap::<i32, &str, 8>::new();
assert!(map.insert_or_update(1, "one").is_ok());
assert!(map.insert_or_update(2, "two").is_ok());
assert!(map.insert_or_update(3, "three").is_ok());
assert_eq!(map.len(), 3);
assert!(!map.is_empty());
assert_eq!(map.get(&1), Some(&"one"));
assert_eq!(map.get(&2), Some(&"two"));
assert_eq!(map.get(&3), Some(&"three"));
assert_eq!(map.get(&4), None);
}
#[test]
fn test_update_existing() {
let mut map = StackMap::<&str, i32, 4>::new();
map.insert_or_update("apple", 5).unwrap();
map.insert_or_update("banana", 10).unwrap();
assert_eq!(map.len(), 2);
map.insert_or_update("apple", 25).unwrap();
map.insert_or_update("banana", 30).unwrap();
assert_eq!(map.len(), 2);
assert_eq!(map.get(&"apple"), Some(&25));
assert_eq!(map.get(&"banana"), Some(&30));
}
#[test]
fn test_remove() {
let mut map = StackMap::<&str, i32, 4>::new();
map.insert_or_update("a", 1).unwrap();
map.insert_or_update("b", 2).unwrap();
assert_eq!(map.len(), 2);
assert_eq!(map.remove(&"a"), Some(1));
assert_eq!(map.len(), 1);
assert_eq!(map.get(&"a"), None);
assert_eq!(map.remove(&"c"), None);
assert_eq!(map.len(), 1);
assert_eq!(map.remove(&"b"), Some(2));
assert_eq!(map.len(), 0);
assert!(map.is_empty());
}
#[test]
fn test_clear() {
let mut map = StackMap::<i32, &str, 8>::new();
map.insert_or_update(1, "one").unwrap();
map.insert_or_update(2, "two").unwrap();
assert_eq!(map.len(), 2);
map.clear();
assert_eq!(map.len(), 0);
assert!(map.is_empty());
assert_eq!(map.get(&1), None);
assert_eq!(map.get(&2), None);
map.insert_or_update(3, "three").unwrap();
assert_eq!(map.len(), 1);
assert_eq!(map.get(&3), Some(&"three"));
}
#[test]
fn test_capacity_limits() {
let mut map = StackMap::<i32, &str, 3>::new();
assert!(map.insert_or_update(1, "one").is_ok());
assert!(map.insert_or_update(2, "two").is_ok());
assert!(map.insert_or_update(3, "three").is_ok());
assert_eq!(map.len(), 3);
assert_eq!(map.insert_or_update(4, "four"), Err(StackMapError::MapFull));
assert_eq!(map.len(), 3);
assert!(map.insert_or_update(1, "ONE").is_ok());
assert_eq!(map.get(&1), Some(&"ONE"));
assert_eq!(map.len(), 3);
assert_eq!(map.remove(&2), Some("two"));
assert_eq!(map.len(), 2);
assert!(map.insert_or_update(4, "four").is_ok());
assert_eq!(map.len(), 3);
assert_eq!(map.get(&4), Some(&"four"));
}
#[test]
fn test_iterator() {
let mut map = StackMap::<i32, &str, 8>::new();
map.insert_or_update(1, "one").unwrap();
map.insert_or_update(2, "two").unwrap();
map.insert_or_update(3, "three").unwrap();
map.remove(&2);
let pairs: Vec<_> = map.iter().collect();
assert_eq!(pairs.len(), 2);
assert!(pairs.contains(&(&1, &"one")));
assert!(!pairs.contains(&(&2, &"two"))); assert!(pairs.contains(&(&3, &"three")));
}
#[test]
fn test_reuse_deleted_slots() {
let mut map = StackMap::<i32, &str, 3>::new();
map.insert_or_update(1, "one").unwrap();
map.insert_or_update(2, "two").unwrap();
map.insert_or_update(3, "three").unwrap();
map.remove(&1);
map.remove(&2);
assert_eq!(map.len(), 1);
map.insert_or_update(4, "four").unwrap();
map.insert_or_update(5, "five").unwrap();
assert_eq!(map.len(), 3);
assert_eq!(map.insert_or_update(6, "six"), Err(StackMapError::MapFull));
assert_eq!(map.get(&1), None);
assert_eq!(map.get(&2), None);
assert_eq!(map.get(&3), Some(&"three"));
assert_eq!(map.get(&4), Some(&"four"));
assert_eq!(map.get(&5), Some(&"five"));
}
#[test]
fn test_no_heap_allocation_on_creation() {
reset_allocation_stats();
let _map = StackMap::<i32, i32, 16>::new();
assert_eq!(get_allocation_count(), 0, "StackMap creation should not allocate on the heap");
assert_eq!(get_allocated_bytes(), 0, "StackMap creation should not use heap memory");
}
#[test]
fn test_no_heap_allocation_during_operations() {
let mut map = StackMap::<i32, i32, 8>::new();
reset_allocation_stats();
map.insert_or_update(1, 100).unwrap();
map.insert_or_update(2, 200).unwrap();
let _ = map.get(&1);
map.remove(&1);
map.insert_or_update(3, 300).unwrap();
let _ = map.iter().collect::<Vec<_>>();
assert_eq!(get_allocation_count(), 1,
"Only collecting to Vec should allocate, not StackMap operations");
}
#[test]
fn test_heap_allocation_with_string_types() {
reset_allocation_stats();
let mut map = StackMap::<String, i32, 4>::new();
let alloc_after_create = get_allocation_count();
reset_allocation_stats();
map.insert_or_update("test".to_string(), 100).unwrap();
assert!(get_allocation_count() > 0,
"String creation should allocate on the heap");
}
#[test]
fn test_no_growth_allocation_when_filled() {
let mut map = StackMap::<i32, i32, 16>::new();
reset_allocation_stats();
for i in 0..16 {
map.insert_or_update(i, i * 10).unwrap();
}
assert_eq!(get_allocation_count(), 0,
"Adding elements to capacity should not cause heap allocations");
}
#[test]
fn test_compare_with_hashmap() {
use std::collections::HashMap;
reset_allocation_stats();
let mut stack_map = StackMap::<i32, i32, 8>::new();
for i in 0..8 {
stack_map.insert_or_update(i, i).unwrap();
}
let stack_map_allocs = get_allocation_count();
reset_allocation_stats();
let mut hash_map = HashMap::with_capacity(8);
for i in 0..8 {
hash_map.insert(i, i);
}
let hash_map_allocs = get_allocation_count();
assert_eq!(stack_map_allocs, 0, "StackMap should not allocate on the heap");
assert!(hash_map_allocs > 0, "HashMap should allocate on the heap");
}
#[test]
fn test_with_custom_types() {
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct Point {
x: i32,
y: i32,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
struct Label {
name: [u8; 16], value: i32,
}
impl Label {
fn new(val: i32) -> Self {
let mut label = Label::default();
label.value = val;
label
}
}
reset_allocation_stats();
let mut map = StackMap::<Point, Label, 4>::new();
map.insert_or_update(Point { x: 1, y: 2 }, Label::new(100)).unwrap();
map.insert_or_update(Point { x: 3, y: 4 }, Label::new(200)).unwrap();
assert_eq!(get_allocation_count(), 0,
"StackMap with custom stack-only types should not allocate");
assert_eq!(map.get(&Point { x: 1, y: 2 }).unwrap().value, 100);
}
}