use core::fmt;
#[derive(Debug, PartialEq, Eq)]
pub struct Buffer<T> {
items: Vec<T>,
max_capacity: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BufferCreateError {
AllocationFailed,
}
impl fmt::Display for BufferCreateError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AllocationFailed => f.write_str("failed to reserve buffer capacity"),
}
}
}
impl std::error::Error for BufferCreateError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BufferPushError<T> {
Full(T),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BufferExtendError {
pub requested: usize,
pub remaining: usize,
}
impl fmt::Display for BufferExtendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"cannot append {} elements; only {} slots remain",
self.requested, self.remaining
)
}
}
impl std::error::Error for BufferExtendError {}
impl<T> Buffer<T> {
pub fn try_with_capacity(max_capacity: usize) -> Result<Self, BufferCreateError> {
let mut items = Vec::new();
items
.try_reserve_exact(max_capacity)
.map_err(|_| BufferCreateError::AllocationFailed)?;
Ok(Self {
items,
max_capacity,
})
}
pub fn try_push(&mut self, item: T) -> Result<(), BufferPushError<T>> {
if self.is_full() {
return Err(BufferPushError::Full(item));
}
self.items.push(item);
Ok(())
}
pub fn try_extend_from_slice(&mut self, values: &[T]) -> Result<(), BufferExtendError>
where
T: Clone,
{
let remaining = self.capacity() - self.len();
if values.len() > remaining {
return Err(BufferExtendError {
requested: values.len(),
remaining,
});
}
self.items.extend_from_slice(values);
Ok(())
}
pub fn pop(&mut self) -> Option<T> {
self.items.pop()
}
pub fn clear(&mut self) {
self.items.clear();
}
#[must_use]
pub fn as_slice(&self) -> &[T] {
self.items.as_slice()
}
#[must_use]
pub fn as_mut_slice(&mut self) -> &mut [T] {
self.items.as_mut_slice()
}
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
#[must_use]
pub const fn capacity(&self) -> usize {
self.max_capacity
}
#[must_use]
pub fn is_full(&self) -> bool {
self.len() == self.capacity()
}
}
#[cfg(test)]
mod tests {
use super::{Buffer, BufferCreateError, BufferExtendError, BufferPushError};
#[test]
fn starts_empty_with_configured_capacity() {
let buffer = Buffer::<u8>::try_with_capacity(3).unwrap();
assert!(buffer.is_empty());
assert!(!buffer.is_full());
assert_eq!(buffer.len(), 0);
assert_eq!(buffer.capacity(), 3);
assert_eq!(buffer.as_slice(), &[]);
}
#[test]
fn preserves_contiguous_insertion_order() {
let mut buffer = Buffer::try_with_capacity(3).unwrap();
buffer.try_push(11).unwrap();
buffer.try_push(22).unwrap();
buffer.try_push(33).unwrap();
assert_eq!(buffer.as_slice(), &[11, 22, 33]);
assert!(buffer.is_full());
}
#[test]
fn extends_contiguously_in_one_bounded_operation() {
let mut buffer = Buffer::try_with_capacity(5).unwrap();
buffer.try_push(1).unwrap();
buffer.try_extend_from_slice(&[2, 3, 4]).unwrap();
assert_eq!(buffer.as_slice(), &[1, 2, 3, 4]);
assert_eq!(buffer.len(), 4);
assert_eq!(buffer.capacity(), 5);
}
#[test]
fn failed_bulk_extend_leaves_contents_unchanged() {
let mut buffer = Buffer::try_with_capacity(2).unwrap();
buffer.try_push(10).unwrap();
let error = buffer.try_extend_from_slice(&[20, 30]).unwrap_err();
assert_eq!(
error,
BufferExtendError {
requested: 2,
remaining: 1
}
);
assert_eq!(buffer.as_slice(), &[10]);
}
#[test]
fn mutable_slice_changes_values_without_changing_shape() {
let mut buffer = Buffer::try_with_capacity(3).unwrap();
buffer.try_extend_from_slice(&[2, 4]).unwrap();
buffer.as_mut_slice()[0] = 7;
assert_eq!(buffer.as_slice(), &[7, 4]);
assert_eq!(buffer.len(), 2);
assert_eq!(buffer.capacity(), 3);
}
#[test]
fn empty_bulk_extend_is_valid_at_zero_capacity() {
let mut buffer = Buffer::<u8>::try_with_capacity(0).unwrap();
assert_eq!(buffer.try_extend_from_slice(&[]), Ok(()));
assert_eq!(buffer.as_slice(), &[]);
}
#[test]
fn full_buffer_returns_item_without_changing_contents() {
let mut buffer = Buffer::try_with_capacity(1).unwrap();
buffer.try_push("kept").unwrap();
assert_eq!(
buffer.try_push("returned"),
Err(BufferPushError::Full("returned"))
);
assert_eq!(buffer.as_slice(), &["kept"]);
}
#[test]
fn capacity_can_be_reused_after_pop_and_clear() {
let mut buffer = Buffer::try_with_capacity(2).unwrap();
buffer.try_push(1).unwrap();
buffer.try_push(2).unwrap();
assert_eq!(buffer.pop(), Some(2));
buffer.try_push(3).unwrap();
buffer.clear();
assert!(buffer.is_empty());
buffer.try_push(4).unwrap();
assert_eq!(buffer.as_slice(), &[4]);
}
#[test]
fn zero_capacity_is_valid_but_rejects_every_item() {
let mut buffer = Buffer::<u8>::try_with_capacity(0).unwrap();
assert!(buffer.is_full());
assert_eq!(buffer.try_push(7), Err(BufferPushError::Full(7)));
assert_eq!(buffer.pop(), None);
}
#[test]
fn impossible_capacity_fails_fallibly() {
assert_eq!(
Buffer::<u8>::try_with_capacity(usize::MAX).unwrap_err(),
BufferCreateError::AllocationFailed
);
}
#[test]
fn zero_sized_types_obey_logical_capacity() {
let mut buffer = Buffer::<()>::try_with_capacity(3).unwrap();
assert_eq!(buffer.capacity(), 3);
for _ in 0..3 {
buffer.try_push(()).unwrap();
}
assert_eq!(buffer.len(), 3);
assert!(buffer.is_full());
assert_eq!(buffer.try_push(()), Err(BufferPushError::Full(())));
assert_eq!(buffer.len(), 3);
assert_eq!(buffer.pop(), Some(()));
assert_eq!(buffer.len(), 2);
}
#[test]
fn over_aligned_elements_keep_their_type_alignment() {
#[repr(align(128))]
#[derive(Debug)]
struct Aligned(u64);
let mut buffer = Buffer::try_with_capacity(2).unwrap();
buffer.try_push(Aligned(17)).unwrap();
buffer.try_push(Aligned(23)).unwrap();
let values = buffer.as_slice();
assert_eq!(
(values.as_ptr() as usize) % core::mem::align_of::<Aligned>(),
0
);
assert_eq!(
(core::ptr::addr_of!(values[1]) as usize) % core::mem::align_of::<Aligned>(),
0
);
assert_eq!(
values.iter().map(|item| item.0).collect::<Vec<_>>(),
[17, 23]
);
}
#[test]
fn clear_and_drop_destroy_each_owned_item_once() {
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Debug)]
struct Tracked(Arc<AtomicUsize>);
impl Drop for Tracked {
fn drop(&mut self) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
let drops = Arc::new(AtomicUsize::new(0));
{
let mut buffer = Buffer::try_with_capacity(2).unwrap();
buffer.try_push(Tracked(Arc::clone(&drops))).unwrap();
buffer.try_push(Tracked(Arc::clone(&drops))).unwrap();
buffer.clear();
assert_eq!(drops.load(Ordering::SeqCst), 2);
buffer.try_push(Tracked(Arc::clone(&drops))).unwrap();
}
assert_eq!(drops.load(Ordering::SeqCst), 3);
}
}