use std::ops::{Deref, DerefMut};
#[derive(Debug, Clone)]
pub enum Small<T: Copy, const N: usize> {
Empty,
Inline {
buf: [T; N],
len: usize,
},
Spilled(Vec<T>),
}
impl<T: Copy, const N: usize> Small<T, N> {
#[must_use]
pub fn new() -> Small<T, N> {
const { assert!(N > 0, "a Small with no inline room is just a Vec") };
Small::Empty
}
pub fn push(&mut self, v: T) {
const { assert!(N > 0, "a Small with no inline room is just a Vec") };
match self {
Small::Empty => {
*self = Small::Inline {
buf: [v; N],
len: 1,
}
}
Small::Inline { buf, len } if *len < N => {
buf[*len] = v;
*len += 1;
}
Small::Inline { buf, len } => {
let mut spill = Vec::with_capacity(N * 2);
spill.extend_from_slice(&buf[..*len]);
spill.push(v);
*self = Small::Spilled(spill);
}
Small::Spilled(s) => s.push(v),
}
}
pub fn collect<I: IntoIterator<Item = T>>(it: I) -> Small<T, N> {
const { assert!(N > 0, "a Small with no inline room is just a Vec") };
let mut it = it.into_iter();
let Some(first) = it.next() else {
return Small::Empty;
};
let mut buf = [first; N];
let mut len = 1;
while let Some(v) = it.next() {
if len == N {
let mut spill = Vec::with_capacity(N * 2);
spill.extend_from_slice(&buf[..len]);
spill.push(v);
spill.extend(it);
return Small::Spilled(spill);
}
buf[len] = v;
len += 1;
}
Small::Inline { buf, len }
}
#[must_use]
pub fn from_slice(s: &[T]) -> Small<T, N> {
const { assert!(N > 0, "a Small with no inline room is just a Vec") };
let Some(&first) = s.first() else {
return Small::Empty;
};
if s.len() > N {
return Small::Spilled(s.to_vec());
}
let mut buf = [first; N];
buf[..s.len()].copy_from_slice(s);
Small::Inline { buf, len: s.len() }
}
pub fn extend_from_slice(&mut self, s: &[T]) {
const { assert!(N > 0, "a Small with no inline room is just a Vec") };
if s.is_empty() {
return;
}
match self {
Small::Empty => *self = Small::from_slice(s),
Small::Inline { buf, len } if *len + s.len() <= N => {
buf[*len..*len + s.len()].copy_from_slice(s);
*len += s.len();
}
Small::Inline { buf, len } => {
let mut spill = Vec::with_capacity((*len + s.len()).max(N * 2));
spill.extend_from_slice(&buf[..*len]);
spill.extend_from_slice(s);
*self = Small::Spilled(spill);
}
Small::Spilled(v) => v.extend_from_slice(s),
}
}
#[must_use]
pub fn as_slice(&self) -> &[T] {
match self {
Small::Empty => &[],
Small::Inline { buf, len } => &buf[..*len],
Small::Spilled(v) => v,
}
}
pub fn as_mut_slice(&mut self) -> &mut [T] {
match self {
Small::Empty => &mut [],
Small::Inline { buf, len } => &mut buf[..*len],
Small::Spilled(v) => v,
}
}
#[must_use]
pub fn is_inline(&self) -> bool {
!matches!(self, Small::Spilled(_))
}
}
#[allow(clippy::derivable_impls)]
impl<T: Copy, const N: usize> Default for Small<T, N> {
fn default() -> Small<T, N> {
Small::Empty
}
}
impl<T: Copy, const N: usize> Deref for Small<T, N> {
type Target = [T];
fn deref(&self) -> &[T] {
self.as_slice()
}
}
impl<T: Copy, const N: usize> DerefMut for Small<T, N> {
fn deref_mut(&mut self) -> &mut [T] {
self.as_mut_slice()
}
}
impl<'a, T: Copy, const N: usize> IntoIterator for &'a Small<T, N> {
type Item = &'a T;
type IntoIter = std::slice::Iter<'a, T>;
fn into_iter(self) -> std::slice::Iter<'a, T> {
self.as_slice().iter()
}
}
impl<'a, T: Copy, const N: usize> IntoIterator for &'a mut Small<T, N> {
type Item = &'a mut T;
type IntoIter = std::slice::IterMut<'a, T>;
fn into_iter(self) -> std::slice::IterMut<'a, T> {
self.as_mut_slice().iter_mut()
}
}
impl<T: Copy, const N: usize> FromIterator<T> for Small<T, N> {
fn from_iter<I: IntoIterator<Item = T>>(it: I) -> Small<T, N> {
Small::collect(it)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_empty_one_is_an_empty_slice() {
let s: Small<u32, 4> = Small::new();
assert!(s.is_empty());
assert_eq!(&*s, &[] as &[u32]);
assert!(s.is_inline());
}
#[test]
fn everything_up_to_n_stays_on_the_stack() {
for n in 1..=4usize {
let s: Small<u32, 4> = Small::collect(0..n as u32);
assert!(s.is_inline(), "{n} elements spilled and should not have");
assert_eq!(&*s, &(0..n as u32).collect::<Vec<_>>()[..]);
}
}
#[test]
fn one_past_n_spills_and_keeps_everything() {
let s: Small<u32, 4> = Small::collect(0..5);
assert!(!s.is_inline(), "five in a four did not spill");
assert_eq!(&*s, &[0, 1, 2, 3, 4]);
}
#[test]
fn a_long_spill_keeps_the_order() {
let s: Small<u32, 4> = Small::collect(0..1_000);
assert!(!s.is_inline());
assert_eq!(s.len(), 1_000);
assert!(s.iter().copied().eq(0..1_000));
}
#[test]
fn the_slice_forms_agree_with_collecting() {
for n in 0..12usize {
let want: Vec<u32> = (0..n as u32).collect();
let s: Small<u32, 4> = Small::from_slice(&want);
assert_eq!(&*s, &want[..], "from_slice at {n}");
assert_eq!(s.is_inline(), n <= 4, "from_slice spilled wrongly at {n}");
for split in 0..=n {
let mut s: Small<u32, 4> = Small::from_slice(&want[..split]);
s.extend_from_slice(&want[split..]);
assert_eq!(&*s, &want[..], "extend at {n} split at {split}");
}
}
}
#[test]
fn extending_past_the_spill_keeps_everything() {
let mut s: Small<u32, 4> = Small::collect(0..6);
s.extend_from_slice(&[6, 7, 8]);
assert!(!s.is_inline());
assert!(s.iter().copied().eq(0..9));
let mut empty: Small<u32, 4> = Small::new();
empty.extend_from_slice(&[]);
assert!(empty.is_empty());
assert!(matches!(empty, Small::Empty));
}
#[test]
fn it_can_be_sorted_in_place_either_way_round() {
let mut small: Small<u32, 4> = Small::collect([3, 1, 2]);
small.sort_unstable();
assert_eq!(&*small, &[1, 2, 3]);
let mut big: Small<u32, 4> = Small::collect([9, 3, 1, 2, 7, 5]);
big.sort_unstable();
assert_eq!(&*big, &[1, 2, 3, 5, 7, 9]);
}
#[test]
fn it_holds_references() {
let owned = [1u32, 2, 3];
let s: Small<&u32, 4> = owned.iter().collect();
assert_eq!(s.iter().copied().copied().collect::<Vec<_>>(), [1, 2, 3]);
}
#[test]
fn the_padding_is_not_part_of_the_slice() {
let s: Small<u32, 8> = Small::collect([7, 8]);
assert_eq!(&*s, &[7, 8]);
assert_eq!(s.len(), 2);
}
}