#![doc(html_root_url = "https://docs.rs/fixed_free_list")]
#![crate_name = "fixed_free_list"]
#![warn(
missing_debug_implementations,
trivial_casts,
trivial_numeric_casts,
unused_lifetimes,
unused_import_braces,
clippy::shadow_unrelated
)]
#![deny(missing_docs, unsafe_op_in_unsafe_fn)]
use std::{
fmt::{Debug, Formatter, Result},
marker::PhantomData,
mem::{self, ManuallyDrop, MaybeUninit},
};
union Block<T> {
value: ManuallyDrop<T>,
next: usize,
}
pub struct FixedFreeList<T, const N: usize> {
next: usize,
high: usize,
data: [MaybeUninit<Block<T>>; N],
}
impl<T, const N: usize> FixedFreeList<T, N> {
#[inline(always)]
pub fn new() -> Self {
Self {
next: N,
high: 0,
data: [const { MaybeUninit::uninit() }; N],
}
}
pub fn alloc(&mut self, value: T) -> Option<usize> {
let key;
if self.next < N {
key = self.next;
self.next = unsafe { self.data.get_unchecked(key).assume_init_ref().next };
} else {
if self.high >= N {
return None;
}
key = self.high;
self.high += 1;
};
unsafe {
*self.data.get_unchecked_mut(key) = MaybeUninit::new(Block {
value: ManuallyDrop::new(value),
});
}
Some(key)
}
pub fn is_free(&self, key: usize) -> bool {
if key >= self.high {
return true;
}
let mut next = self.next;
while next < N {
if next == key {
return true;
}
next = unsafe { self.data.get_unchecked(next).assume_init_ref().next };
}
false
}
#[inline(always)]
pub unsafe fn free_unchecked(&mut self, key: usize) -> T {
#[cfg(feature = "strict")]
assert!(!self.is_free(key));
let value = mem::replace(
unsafe { self.data.get_unchecked_mut(key) },
MaybeUninit::new(Block { next: self.next }),
);
self.next = key;
ManuallyDrop::into_inner(unsafe { value.assume_init().value })
}
#[inline(always)]
pub unsafe fn get_unchecked(&self, key: usize) -> &T {
#[cfg(feature = "strict")]
assert!(!self.is_free(key));
unsafe { &self.data.get_unchecked(key).assume_init_ref().value }
}
#[inline(always)]
pub unsafe fn get_mut_unchecked(&mut self, key: usize) -> &mut T {
#[cfg(feature = "strict")]
assert!(!self.is_free(key));
unsafe { &mut self.data.get_unchecked_mut(key).assume_init_mut().value }
}
#[inline(always)]
pub fn size_hint(&self) -> usize {
self.high
}
#[inline(always)]
pub fn is_full(&self) -> bool {
self.high == N && self.next == N
}
pub fn clear(&mut self) {
if mem::needs_drop::<T>() {
let mut free = [false; N];
let mut next = self.next;
while next < N {
free[next] = true;
next = unsafe { self.data.get_unchecked(next).assume_init_ref().next };
}
for (i, &is_free) in free.iter().enumerate().take(self.high) {
if !is_free {
unsafe {
ManuallyDrop::drop(
&mut self.data.get_unchecked_mut(i).assume_init_mut().value,
);
}
}
}
}
self.high = 0;
self.next = N;
}
}
unsafe impl<T: Sync, const N: usize> Sync for FixedFreeList<T, N> {}
unsafe impl<T: Send, const N: usize> Send for FixedFreeList<T, N> {}
impl<T, const N: usize> Drop for FixedFreeList<T, N> {
#[inline(always)]
fn drop(&mut self) {
if mem::needs_drop::<T>() {
self.clear();
}
}
}
#[derive(Debug)]
enum Space<T: Sized> {
Value(T),
Free(#[allow(dead_code)] usize),
Uninit,
}
impl<T, const N: usize> Debug for FixedFreeList<T, N>
where
T: Debug,
{
fn fmt(&self, f: &mut Formatter) -> Result {
let mut spaces = std::array::from_fn::<Space<&T>, N, _>(|_| Space::Uninit);
let mut next = self.next;
while next < N {
let free = unsafe { self.data.get_unchecked(next).assume_init_ref().next };
spaces[next] = Space::Free(free);
next = free;
}
for (i, space) in spaces.iter_mut().enumerate().take(self.high) {
if let Space::Uninit = space {
*space = Space::Value(unsafe { &self.data[i].assume_init_ref().value });
}
}
f.debug_struct("FixedFreeList")
.field("next", &self.next)
.field("high", &self.high)
.field("data", &spaces)
.finish()
}
}
impl<T, const N: usize> Default for FixedFreeList<T, N> {
fn default() -> Self {
Self::new()
}
}
pub struct ArenaKey<'a, T> {
index: usize,
_marker: PhantomData<&'a T>,
}
impl<'a, T> Debug for ArenaKey<'a, T> {
fn fmt(&self, f: &mut Formatter) -> Result {
f.debug_struct("ArenaKey")
.field("index", &self.index)
.finish()
}
}
pub struct SafeFixedFreeList<'a, T, const N: usize, U> {
_marker: PhantomData<&'a U>,
inner: FixedFreeList<T, N>,
}
impl<'a, T, const N: usize, U: Fn()> SafeFixedFreeList<'a, T, N, U> {
#[inline(always)]
pub unsafe fn new(_: U) -> Self {
Self {
_marker: PhantomData,
inner: FixedFreeList::new(),
}
}
#[inline(always)]
pub fn alloc<'k>(&mut self, value: T) -> Option<ArenaKey<'k, U>>
where
'a: 'k,
{
self.inner.alloc(value).map(|index| ArenaKey {
index,
_marker: PhantomData,
})
}
#[inline(always)]
pub fn free(&mut self, key: ArenaKey<U>) -> T {
unsafe { self.inner.free_unchecked(key.index) }
}
#[inline(always)]
pub fn get<'k: 'v, 'v>(&self, key: &'k ArenaKey<U>) -> &'v T
where
'a: 'k,
{
unsafe { mem::transmute::<&T, &T>(self.inner.get_unchecked(key.index)) }
}
#[inline(always)]
pub fn get_mut<'k: 'v, 'v>(&mut self, key: &'k mut ArenaKey<U>) -> &'v mut T
where
'a: 'k,
{
unsafe { mem::transmute::<&mut T, &mut T>(self.inner.get_mut_unchecked(key.index)) }
}
#[inline(always)]
pub fn size_hint(&self) -> usize {
self.inner.size_hint()
}
#[inline(always)]
pub fn is_full(&self) -> bool {
self.inner.is_full()
}
#[inline(always)]
pub unsafe fn clear(&mut self) {
self.inner.clear();
}
}
impl<'a, T, const N: usize, U: Fn()> Debug for SafeFixedFreeList<'a, T, N, U>
where
T: Debug,
{
fn fmt(&self, f: &mut Formatter) -> Result {
f.debug_struct("SafeFixedFreeList")
.field("inner", &self.inner)
.finish()
}
}
#[cfg(test)]
mod test {
use crate::*;
use rand::Rng;
use std::cell::RefCell;
#[test]
fn test_default() {
let heap: FixedFreeList<i32, 4> = FixedFreeList::default();
assert_eq!(0, heap.high);
}
#[test]
fn test_safe() {
fn consume<T>(_: T) {}
let mut list = unsafe { SafeFixedFreeList::<u32, 4, _>::new(|| ()) };
assert_eq!(0, list.size_hint());
let mut key1 = list.alloc(5).unwrap();
let mut key2 = list.alloc(6).unwrap();
assert_eq!(2, list.size_hint());
let value1 = list.get_mut(&mut key1);
let value2 = list.get_mut(&mut key2);
*value1 = 2;
consume(value1);
consume(value2);
assert_eq!(&2, list.get(&key1));
assert_eq!(&6, list.get(&key2));
assert!(!list.is_full());
assert_eq!(format!("{:?}", key1), "ArenaKey { index: 0 }");
list.free(key1);
list.free(key2);
assert_eq!(2, list.size_hint());
assert_eq!(
format!("{:?}", list),
"SafeFixedFreeList { inner: FixedFreeList { next: 1, high: 2, data: [Free(4), Free(0), Uninit, Uninit] } }"
);
unsafe { list.clear() };
assert_eq!(0, list.size_hint());
consume(list);
}
#[test]
fn test_get_unchecked() {
let mut list = FixedFreeList::<usize, 16>::new();
for i in 0..16 {
assert_eq!(list.alloc(i), Some(i));
}
for i in 0..16 {
unsafe {
assert_eq!(list.get_unchecked(i), &i);
}
}
for i in 0..16 {
unsafe {
*list.get_mut_unchecked(i) = i * 2;
}
}
for i in 0..16 {
unsafe {
assert_eq!(list.get_unchecked(i), &(i * 2));
}
}
assert!(list.is_full());
}
#[test]
fn test_remove_all() {
let mut list = FixedFreeList::<usize, 16>::new();
for i in 0..16 {
assert_eq!(list.alloc(i), Some(i));
}
for i in 0..16 {
unsafe {
assert_eq!(list.free_unchecked(i), i);
}
}
for i in 0..17 {
assert!(list.is_free(i));
}
for i in 0..16 {
list.alloc(i);
}
assert!(list.is_full());
}
#[test]
fn test_reuse_half() {
let mut list = FixedFreeList::<usize, 16>::new();
for i in 0..16 {
assert_eq!(list.alloc(i), Some(i));
}
for i in 0..8 {
unsafe {
list.free_unchecked(i * 2);
}
}
for i in 0..8 {
list.alloc(i);
}
assert!(list.is_full());
}
#[test]
fn test_reuse_preferred() {
let mut list = FixedFreeList::<u32, 16>::new();
for i in 0..8 {
assert_eq!(list.alloc(i), Some(i as usize));
}
for i in 0..4 {
unsafe {
list.free_unchecked(i * 2usize);
}
}
for i in 0..4 {
list.alloc(i);
}
assert_eq!(list.size_hint(), 8);
for i in 8..16 {
assert_eq!(list.alloc(i), Some(i as usize));
}
assert!(list.is_full());
}
#[test]
fn test_debug() {
let mut list = FixedFreeList::<u32, 8>::new();
list.alloc(3);
let key1 = list.alloc(5).unwrap();
list.alloc(7);
list.alloc(4);
let key2 = list.alloc(2).unwrap();
unsafe {
list.free_unchecked(key1);
list.free_unchecked(key2);
}
assert_eq!(
format!("{:?}", list),
"FixedFreeList { next: 4, high: 5, data: [Value(3), Free(8), Value(7), Value(4), Free(1), Uninit, Uninit, Uninit] }"
);
}
#[test]
fn test_full() {
let mut list = FixedFreeList::<u32, 4>::new();
assert_eq!(list.alloc(3), Some(0));
assert_eq!(list.alloc(5), Some(1));
assert_eq!(list.alloc(7), Some(2));
assert_eq!(list.alloc(4), Some(3));
assert_eq!(list.alloc(2), None);
}
#[test]
fn test_drop() {
let drops = RefCell::new(0usize);
{
let mut list: FixedFreeList<DropCounted, 16> = FixedFreeList::new();
for _ in 0..11 {
list.alloc(DropCounted(&drops));
}
assert_eq!(*drops.borrow(), 0);
for i in 0..4 {
unsafe {
list.free_unchecked(i);
}
}
assert_eq!(*drops.borrow(), 4);
}
assert_eq!(*drops.borrow(), 11);
{
let mut list: FixedFreeList<DropCounted, 1> = FixedFreeList::new();
list.alloc(DropCounted(&drops));
list.alloc(DropCounted(&drops));
assert_eq!(*drops.borrow(), 12);
}
assert_eq!(*drops.borrow(), 13);
}
struct DropCounted<'a>(&'a RefCell<usize>);
impl<'a> Drop for DropCounted<'a> {
fn drop(&mut self) {
*self.0.borrow_mut() += 1;
}
}
#[test]
fn test_fuzz() {
fuzz::<0, 4>(10);
fuzz::<1, 8>(10);
fuzz::<2, 8>(10);
fuzz::<3, 8>(10);
fuzz::<5, 8>(10);
fuzz::<7, 16>(10);
fuzz::<16, 16>(10);
fuzz::<16, 64>(10);
}
fn fuzz<const N: usize, const M: usize>(iters: usize) {
for _ in 0..iters {
let mut list: FixedFreeList<usize, N> = FixedFreeList::new();
let mut keys = Vec::new();
let mut count = 0;
let mut high = 0;
for i in 0..M {
if rand::rng().random() {
if let Some(key) = list.alloc(i) {
keys.push(key);
count += 1;
if high < count {
high = count;
}
} else {
assert_eq!(count, N);
}
} else if count > 0 {
let key = keys.swap_remove(rand::rng().random_range(0..keys.len()));
unsafe {
assert!(!list.is_free(key));
list.free_unchecked(key);
assert!(list.is_free(key));
count -= 1;
}
}
assert_eq!(list.size_hint(), high);
}
}
}
}