circularing 3.0.0

Circular buffer backed by in-stack storage.
Documentation
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]
#![feature(maybe_uninit_uninit_array)]
#![feature(const_maybe_uninit_uninit_array)]
#![feature(const_maybe_uninit_write)]
#![no_std]

use core::fmt::{self, Debug};
use core::mem::MaybeUninit;

use const_assert::{Assert, IsTrue};
use likeness::unlikely;

pub struct Circular<const N: usize, T>
where
    T: Unpin,
{
    target_buffer: [MaybeUninit<T>; N],
    target_head: usize,
    target_tail: usize,
}

impl<const N: usize, T> Circular<N, T>
where
    T: Unpin,
    Assert<{ N != 0 }>: IsTrue,
{
    #[inline]
    pub const fn new() -> Self {
        let target_buffer = MaybeUninit::uninit_array();

        let target_head = usize::MIN;

        let target_tail = target_head;

        Self {
            target_buffer,
            target_head,
            target_tail,
        }
    }

    /// Pushes an element into the buffer.
    #[inline]
    pub fn push(&mut self, target_value: T) {
        let Self {
            target_buffer,
            target_head,
            target_tail,
        } = self;

        // SAFETY: Arbitrary write, posterior modulo operations guarantee that this is in-bounds.
        unsafe {
            target_buffer
                .get_unchecked_mut(*target_head)
                .write(target_value);
        }

        *target_head = target_head.saturating_add(1);
        *target_head %= N;

        if unlikely(*target_head == *target_tail) {
            *target_tail = target_tail.saturating_add(1);
            *target_tail %= N;
        }
    }

    /// Pops an element from the buffer.
    #[inline]
    pub fn pop(&mut self) -> Option<T> {
        let Self {
            target_buffer,
            target_head,
            target_tail,
        } = self;

        if unlikely(*target_head == *target_tail) {
            return None;
        }

        // SAFETY: Guaranteed to be a valid item due to previous bounds check.
        let target_value = unsafe {
            Some(
                target_buffer
                    .get_unchecked_mut(*target_tail)
                    .assume_init_read(),
            )
        };

        *target_tail = target_tail.saturating_add(1);
        *target_tail %= N;

        target_value
    }

    /// Determines if the current buffer is empty.
    #[inline]
    pub const fn is_empty(&self) -> bool {
        let Self {
            target_head,
            target_tail,
            ..
        } = self;

        *target_head == *target_tail
    }
}

impl<const N: usize, T: Clone> Clone for Circular<N, T>
where
    T: Unpin + Clone,
    MaybeUninit<T>: Clone,
{
    fn clone(&self) -> Self {
        Self {
            target_buffer: self.target_buffer.clone(),
            target_head: self.target_head.clone(),
            target_tail: self.target_tail.clone(),
        }
    }
}

impl<const N: usize, T: Debug> Debug for Circular<N, T>
where
    T: Unpin,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Circular")
            .field("target_buffer", &self.target_buffer)
            .field("target_head", &self.target_head)
            .field("target_tail", &self.target_tail)
            .finish()
    }
}

#[cfg(test)]
mod tests {
    use crate::Circular;

    #[test]
    fn push_and_pop() {
        let mut last_scores: Circular<16, usize> = Circular::new();

        {
            last_scores.push(1);

            assert!(matches!(last_scores.pop(), Some(1)));
        }

        {
            assert!(matches!(last_scores.pop(), None));
        }
    }

    #[test]
    fn wrapping_behaviour() {
        const DISCRIMINANT_VALUE: usize = 0x10;
        const COMMONFOUND_VALUE: usize = 0x20;

        const ARRAY_SIZE: usize = 0x10;

        let mut last_scores: Circular<ARRAY_SIZE, usize> = Circular::new();

        {
            for _ in 0..ARRAY_SIZE {
                last_scores.push(COMMONFOUND_VALUE);
            }

            while let Some(target_value) = last_scores.pop() {
                assert_ne!(target_value, DISCRIMINANT_VALUE)
            }
        }

        {
            assert!(matches!(last_scores.pop(), None));
        }
    }
}