use core::fmt;
use core::mem::MaybeUninit;
use core::ops::{Index, IndexMut};
pub struct FixedVec<T, const N: usize> {
storage: Storage<T, N>,
len: usize,
capacity: usize,
}
enum Storage<T, const N: usize> {
Stack([MaybeUninit<T>; N]),
Heap(alloc::boxed::Box<[MaybeUninit<T>]>),
}
impl<T, const N: usize> FixedVec<T, N> {
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
let storage = if capacity <= N {
Storage::Stack(unsafe { MaybeUninit::uninit().assume_init() })
} else {
let mut vec = alloc::vec::Vec::with_capacity(capacity);
unsafe {
vec.set_len(capacity);
}
Storage::Heap(vec.into_boxed_slice())
};
Self {
storage,
len: 0,
capacity,
}
}
#[inline]
#[allow(unused)]
pub fn capacity(&self) -> usize {
self.capacity
}
#[inline]
#[allow(unused)]
pub fn len(&self) -> usize {
self.len
}
#[inline]
#[allow(unused)]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub unsafe fn set_len(&mut self, new_len: usize) {
debug_assert!(new_len <= self.capacity);
self.len = new_len;
}
#[inline]
pub fn as_ptr(&self) -> *const MaybeUninit<T> {
match &self.storage {
Storage::Stack(arr) => arr.as_ptr(),
Storage::Heap(boxed) => boxed.as_ptr(),
}
}
#[inline(always)]
pub unsafe fn get_unchecked_ptr(&self, index: usize) -> *const MaybeUninit<T> {
match &self.storage {
Storage::Stack(arr) => unsafe { arr.as_ptr().add(index) },
Storage::Heap(boxed) => unsafe { boxed.as_ptr().add(index) },
}
}
#[inline(always)]
pub unsafe fn get_unchecked_mut_ptr(&mut self, index: usize) -> *mut MaybeUninit<T> {
match &mut self.storage {
Storage::Stack(arr) => unsafe { arr.as_mut_ptr().add(index) },
Storage::Heap(boxed) => unsafe { boxed.as_mut_ptr().add(index) },
}
}
#[inline]
unsafe fn get_ptr(&self, index: usize) -> *const MaybeUninit<T> {
debug_assert!(index < self.capacity);
unsafe { self.get_unchecked_ptr(index) }
}
#[inline]
unsafe fn get_mut_ptr(&mut self, index: usize) -> *mut MaybeUninit<T> {
debug_assert!(index < self.capacity);
unsafe { self.get_unchecked_mut_ptr(index) }
}
}
impl<T, const N: usize> Index<usize> for FixedVec<T, N> {
type Output = MaybeUninit<T>;
#[inline]
fn index(&self, index: usize) -> &Self::Output {
assert!(index < self.capacity, "index out of bounds");
unsafe { &*self.get_ptr(index) }
}
}
impl<T, const N: usize> IndexMut<usize> for FixedVec<T, N> {
#[inline]
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
assert!(index < self.capacity, "index out of bounds");
unsafe { &mut *self.get_mut_ptr(index) }
}
}
impl<T: Clone, const N: usize> Clone for FixedVec<T, N> {
fn clone(&self) -> Self {
let mut new_vec = Self::with_capacity(self.capacity);
unsafe {
for i in 0..self.len {
let val_uninit = &*self.get_unchecked_ptr(i);
let val_ref = val_uninit.assume_init_ref();
new_vec
.get_unchecked_mut_ptr(i)
.write(MaybeUninit::new(val_ref.clone()));
}
new_vec.set_len(self.len);
}
new_vec
}
}
impl<T: fmt::Debug, const N: usize> fmt::Debug for FixedVec<T, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FixedVec")
.field("len", &self.len)
.field("capacity", &self.capacity)
.field(
"storage",
match &self.storage {
Storage::Stack(_) => &"Stack",
Storage::Heap(_) => &"Heap",
},
)
.finish()
}
}
unsafe impl<T: Send, const N: usize> Send for FixedVec<T, N> {}
unsafe impl<T: Sync, const N: usize> Sync for FixedVec<T, N> {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_small_capacity_stack() {
let vec: FixedVec<i32, 32> = FixedVec::with_capacity(16);
assert_eq!(vec.capacity(), 16);
assert_eq!(vec.len(), 0);
assert!(vec.is_empty());
assert!(matches!(vec.storage, Storage::Stack(_)));
}
#[test]
fn test_large_capacity_heap() {
let vec: FixedVec<i32, 32> = FixedVec::with_capacity(64);
assert_eq!(vec.capacity(), 64);
assert_eq!(vec.len(), 0);
assert!(vec.is_empty());
assert!(matches!(vec.storage, Storage::Heap(_)));
}
#[test]
fn test_exact_threshold() {
let vec: FixedVec<i32, 32> = FixedVec::with_capacity(32);
assert_eq!(vec.capacity(), 32);
assert!(matches!(vec.storage, Storage::Stack(_)));
}
#[test]
fn test_set_len() {
let mut vec: FixedVec<i32, 32> = FixedVec::with_capacity(16);
unsafe {
vec.set_len(10);
}
assert_eq!(vec.len(), 10);
assert!(!vec.is_empty());
}
#[test]
fn test_index_access() {
let mut vec: FixedVec<i32, 32> = FixedVec::with_capacity(8);
unsafe {
vec[0].as_mut_ptr().write(42);
vec[1].as_mut_ptr().write(99);
vec.set_len(2);
assert_eq!(*vec[0].as_ptr(), 42);
assert_eq!(*vec[1].as_ptr(), 99);
}
}
#[test]
fn test_index_mut() {
let mut vec: FixedVec<i32, 32> = FixedVec::with_capacity(8);
unsafe {
vec[0].as_mut_ptr().write(10);
vec[1].as_mut_ptr().write(20);
vec.set_len(2);
*vec[0].as_mut_ptr() = 100;
assert_eq!(*vec[0].as_ptr(), 100);
assert_eq!(*vec[1].as_ptr(), 20);
}
}
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_index_out_of_bounds() {
let vec: FixedVec<i32, 32> = FixedVec::with_capacity(8);
let _ = &vec[10];
}
#[test]
fn test_stack_vs_heap() {
let stack_vec: FixedVec<u8, 32> = FixedVec::with_capacity(32);
let heap_vec: FixedVec<u8, 32> = FixedVec::with_capacity(64);
assert!(matches!(stack_vec.storage, Storage::Stack(_)));
assert!(matches!(heap_vec.storage, Storage::Heap(_)));
}
#[test]
fn test_zero_capacity() {
let vec: FixedVec<i32, 32> = FixedVec::with_capacity(0);
assert_eq!(vec.capacity(), 0);
assert_eq!(vec.len(), 0);
assert!(vec.is_empty());
}
}