ringobuf 0.1.0

Stack based ring buffer (no_std)
Documentation
//! Stack based ring buffer.
#![no_std]
#![allow(incomplete_features)]
#![feature(generic_const_exprs)]

mod test;

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

/// RingBuf is a stack allocated ring buffer.
///
/// The size of the buffer is guaranteed to be a power of 2.
///
/// - **SIZE_BITS**: the size of the ring buffer, expressed in power of 2.
///   eg. 4 would create a ring buffer of size 16.
///   Using a power of 2 for size allows to avoid modulo division and take
///   advantage of bitmasking instead.
///
/// - **T**: Any sized type.
///
/// Example:
///
/// ```
/// #![feature(generic_const_exprs)]
/// use ringobuf::RingBuf;
///
/// let mut r = RingBuf::<2, u8>::new();
/// r.push(1);
/// r.pop();
/// ```
pub struct RingBuf<const SIZE_BITS: usize, T>
where
    [(); 1 << SIZE_BITS]:,
{
    read: usize,
    write: usize,
    size: usize,
    data: [T; 1 << SIZE_BITS],
}

impl<const SIZE_BITS: usize, T> Default for RingBuf<SIZE_BITS, T>
where
    [(); 1 << SIZE_BITS]:,
{
    fn default() -> Self {
        Self {
            read: 0,
            write: 0,
            size: 0,
            // Safety the array is only accessed internally from the indexing
            // and the buffer ensures only initialized memory is returned.
            #[allow(clippy::uninit_assumed_init)]
            data: unsafe { MaybeUninit::uninit().assume_init() },
        }
    }
}

impl<const SIZE_BITS: usize, T> RingBuf<SIZE_BITS, T>
where
    [(); 1 << SIZE_BITS]:,
{
    const MAX: usize = 1 << SIZE_BITS;
    const MASK: usize = (1 << SIZE_BITS) - 1;

    pub fn new() -> Self {
        Self::default()
    }

    pub fn is_empty(&self) -> bool {
        self.size == 0
    }

    pub fn is_full(&self) -> bool {
        self.size == Self::MAX
    }

    pub fn push(&mut self, item: T) -> Result<(), T> {
        if self.is_full() {
            return Err(item);
        }

        self.data[self.write] = item;

        self.size += 1;
        self.write = (self.write + 1) & Self::MASK;

        Ok(())
    }

    #[allow(clippy::result_unit_err)]
    pub fn pop(&mut self) -> Result<T, ()> {
        if self.is_empty() {
            return Err(());
        }

        // Safety swapping uninitialized memory in the array is fine because
        // blocks out of index are not accessible and the array is private.
        #[allow(clippy::uninit_assumed_init)]
        let mut result: T = unsafe { MaybeUninit::uninit().assume_init() };
        // Using swap here because T is not required to be [Copy],
        // so cannot use [mem::copy].
        mem::swap(&mut result, &mut self.data[self.read]);

        self.size -= 1;
        self.read = (self.read + 1) & Self::MASK;

        Ok(result)
    }
}