#![forbid(
missing_docs,
unsafe_op_in_unsafe_fn,
clippy::missing_safety_doc,
clippy::multiple_unsafe_ops_per_block
)]
#![cfg_attr(not(test), forbid(clippy::undocumented_unsafe_blocks))]
#![cfg_attr(docsrs, feature(doc_cfg))]
mod str;
use crossbeam_utils::CachePadded;
#[cfg(feature = "get-size2")]
use get_size2::{GetSize, GetSizeTracker};
use std::mem::MaybeUninit;
use std::ops::{Index, Range};
use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use std::sync::{Mutex, MutexGuard};
pub use str::AppendStr;
pub struct AppendVec<T> {
len: CachePadded<AtomicUsize>,
bucket_offset: u32,
buckets: [AtomicPtr<T>; usize::BITS as usize],
write_lock: Mutex<()>,
}
unsafe impl<T: Send + Sync> Send for AppendVec<T> {}
unsafe impl<T: Send + Sync> Sync for AppendVec<T> {}
impl<T> Default for AppendVec<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(feature = "get-size2")]
impl<T: GetSize> GetSize for AppendVec<T> {
fn get_heap_size_with_tracker<Tr: GetSizeTracker>(&self, tracker: Tr) -> (usize, Tr) {
let iter = self.iter();
let mut allocated_items = 0;
if iter.len != 0 {
let (max_bucket, _) = bucketize(iter.len - 1, iter.bucket_offset);
allocated_items += bucket_len(0, iter.bucket_offset);
for bucket in iter.bucket_offset as usize..=max_bucket {
allocated_items += bucket_len(bucket, iter.bucket_offset);
}
}
let allocated_size = allocated_items * T::get_stack_size();
let (size, tracker) = iter.fold((0, tracker), |(size, tracker), item| {
let (item_size, tracker) = T::get_heap_size_with_tracker(item, tracker);
(size + item_size, tracker)
});
(size + allocated_size, tracker)
}
}
impl<T> AppendVec<T> {
const ITEM_SIZE_LOG2: u32 = std::mem::size_of::<T>().next_power_of_two().ilog2();
const MAX_LEN: usize = !0 >> (Self::ITEM_SIZE_LOG2 + 1);
pub fn new() -> Self {
Self {
len: CachePadded::new(AtomicUsize::new(0)),
bucket_offset: 1,
buckets: [const { AtomicPtr::new(std::ptr::null_mut()) }; usize::BITS as usize],
write_lock: Mutex::new(()),
}
}
pub fn with_capacity(capacity: usize) -> Self {
assert!(
capacity <= Self::MAX_LEN,
"AppendVec: requested capacity is too large for the given type ({capacity}, {})",
Self::MAX_LEN
);
let mut buckets = [std::ptr::null_mut(); usize::BITS as usize];
let bucket_offset = if capacity == 0 {
1
} else {
let bucket_offset = capacity.next_power_of_two().ilog2();
debug_assert_eq!(bucketize(capacity - 1, bucket_offset).0, 0);
let bucket_len = 1 << bucket_offset;
let allocated = Box::<[T]>::new_uninit_slice(bucket_len);
let bucket_ptr = Box::into_raw(allocated) as *mut MaybeUninit<T> as *mut T;
buckets[0] = bucket_ptr;
bucket_offset
};
Self {
len: CachePadded::new(AtomicUsize::new(0)),
bucket_offset,
buckets: buckets.map(AtomicPtr::new),
write_lock: Mutex::new(()),
}
}
#[expect(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
self.len.load(Ordering::Acquire)
}
pub unsafe fn len_unsynchronized(&self) -> usize {
self.len.load(Ordering::Relaxed)
}
pub fn push(&self, t: T) -> usize {
let guard = self.write_lock.lock().unwrap();
let index = self.len.load(Ordering::Relaxed);
if index == Self::MAX_LEN {
drop(guard);
panic!("AppendVec is full: cannot push");
}
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
let bucket_ptr = self.get_bucket_ptr(bucket, &guard);
let ptr = unsafe { bucket_ptr.add(bucket_index) };
unsafe { std::ptr::write(ptr, t) };
self.len.store(index + 1, Ordering::Release);
drop(guard);
index
}
pub fn push_mut(&mut self, t: T) -> usize {
let index = self.len.load(Ordering::Relaxed);
assert_ne!(index, Self::MAX_LEN, "AppendVec is full: cannot push");
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
let bucket_ptr = self.get_bucket_ptr_mut(bucket);
let ptr = unsafe { bucket_ptr.add(bucket_index) };
unsafe { std::ptr::write(ptr, t) };
self.len.store(index + 1, Ordering::Release);
index
}
pub unsafe fn get_unchecked(&self, index: usize) -> &T {
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
let bucket_ptr = self.buckets[bucket].load(Ordering::Relaxed) as *const T;
debug_assert_ne!(bucket_ptr, std::ptr::null());
let ptr = unsafe { bucket_ptr.add(bucket_index) };
unsafe { &*ptr }
}
pub fn iter(&self) -> AppendVecIter<'_, T> {
AppendVecIter {
inner: self,
bucket_offset: self.bucket_offset,
len: self.len.load(Ordering::Acquire),
index: 0,
bucket_ptr: std::ptr::null(),
}
}
pub fn iter_chunks(&self) -> AppendVecChunksIter<'_, T> {
let len = self.len.load(Ordering::Acquire);
let (bucket, (max_bucket, max_index)) = if len == 0 {
(self.bucket_offset as usize, (0, 0))
} else {
(0, bucketize(len - 1, self.bucket_offset))
};
AppendVecChunksIter {
inner: self,
bucket_offset: self.bucket_offset,
bucket,
max_bucket,
max_index,
}
}
fn get_bucket_ptr_mut(&mut self, bucket: usize) -> *mut T {
unsafe { self.get_bucket_ptr_raw(bucket) }
}
fn get_bucket_ptr(&self, bucket: usize, _guard: &MutexGuard<()>) -> *mut T {
unsafe { self.get_bucket_ptr_raw(bucket) }
}
unsafe fn get_bucket_ptr_raw(&self, bucket: usize) -> *mut T {
let ptr = self.buckets[bucket].load(Ordering::Relaxed);
if !ptr.is_null() {
ptr
} else {
let bucket_len = bucket_len(bucket, self.bucket_offset);
let allocated = Box::<[T]>::new_uninit_slice(bucket_len);
let bucket_ptr = Box::into_raw(allocated) as *mut MaybeUninit<T> as *mut T;
self.buckets[bucket].store(bucket_ptr, Ordering::Relaxed);
bucket_ptr
}
}
}
impl<T: Default> AppendVec<T> {
pub fn push_owned_slice(&self, owned_slice: Vec<T>) -> Range<usize> {
unsafe { self.push_contiguous(owned_slice.into_iter()) }
}
pub fn push_owned_slice_mut(&mut self, owned_slice: Vec<T>) -> Range<usize> {
unsafe { self.push_contiguous_mut(owned_slice.into_iter()) }
}
pub fn push_array<const N: usize>(&self, array: [T; N]) -> Range<usize> {
unsafe { self.push_contiguous(array.into_iter()) }
}
pub fn push_array_mut<const N: usize>(&mut self, array: [T; N]) -> Range<usize> {
unsafe { self.push_contiguous_mut(array.into_iter()) }
}
pub unsafe fn push_contiguous(&self, iter: impl ExactSizeIterator<Item = T>) -> Range<usize> {
let iter_len = iter.len();
if iter_len == 0 {
return 0..0;
}
let guard = self.write_lock.lock().unwrap();
let (guard, index) = self.prepare_contiguous_slice(iter_len, guard);
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
debug_assert!(iter_len <= bucket_len(bucket, self.bucket_offset) - bucket_index);
let bucket_ptr = self.get_bucket_ptr(bucket, &guard);
unsafe { self.move_iterator(bucket_ptr, bucket_index, iter) };
self.len.store(index + iter_len, Ordering::Release);
drop(guard);
index..index + iter_len
}
pub unsafe fn push_contiguous_mut(
&mut self,
iter: impl ExactSizeIterator<Item = T>,
) -> Range<usize> {
let iter_len = iter.len();
if iter_len == 0 {
return 0..0;
}
let index = self.prepare_contiguous_slice_mut(iter_len);
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
debug_assert!(iter_len <= bucket_len(bucket, self.bucket_offset) - bucket_index);
let bucket_ptr = self.get_bucket_ptr_mut(bucket);
unsafe { self.move_iterator(bucket_ptr, bucket_index, iter) };
self.len.store(index + iter_len, Ordering::Release);
index..index + iter_len
}
unsafe fn move_iterator(
&self,
bucket_ptr: *mut T,
bucket_index: usize,
iter: impl ExactSizeIterator<Item = T>,
) {
for (i, item) in iter.enumerate() {
let ptr = unsafe { bucket_ptr.add(bucket_index + i) };
unsafe { std::ptr::write(ptr, item) };
}
}
fn prepare_contiguous_slice_mut(&mut self, slice_len: usize) -> usize {
let mut index = self.len.load(Ordering::Relaxed);
assert!(
slice_len <= Self::MAX_LEN - index,
"AppendVec is full: cannot push"
);
loop {
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
let bucket_len = bucket_len(bucket, self.bucket_offset);
if slice_len <= bucket_len - bucket_index {
break;
}
let bucket_ptr = self.get_bucket_ptr_mut(bucket);
for i in bucket_index..bucket_len {
let ptr = unsafe { bucket_ptr.add(i) };
unsafe { std::ptr::write(ptr, T::default()) };
}
index += bucket_len - bucket_index;
}
index
}
fn prepare_contiguous_slice<'guard>(
&self,
slice_len: usize,
guard: MutexGuard<'guard, ()>,
) -> (MutexGuard<'guard, ()>, usize) {
let mut index = self.len.load(Ordering::Relaxed);
if slice_len > Self::MAX_LEN - index {
drop(guard);
panic!("AppendVec is full: cannot push");
}
loop {
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
let bucket_len = bucket_len(bucket, self.bucket_offset);
if slice_len <= bucket_len - bucket_index {
break;
}
let bucket_ptr = self.get_bucket_ptr(bucket, &guard);
for i in bucket_index..bucket_len {
let ptr = unsafe { bucket_ptr.add(i) };
unsafe { std::ptr::write(ptr, T::default()) };
}
index += bucket_len - bucket_index;
}
(guard, index)
}
}
impl<T: Clone + Default> AppendVec<T> {
pub fn push_slice(&self, slice: &[T]) -> Range<usize> {
if slice.is_empty() {
return 0..0;
}
let guard = self.write_lock.lock().unwrap();
let (guard, index) = self.prepare_contiguous_slice(slice.len(), guard);
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
debug_assert!(slice.len() <= bucket_len(bucket, self.bucket_offset) - bucket_index);
let bucket_ptr = self.get_bucket_ptr(bucket, &guard);
unsafe { self.clone_slice(bucket_ptr, bucket_index, slice) };
self.len.store(index + slice.len(), Ordering::Release);
drop(guard);
index..index + slice.len()
}
pub fn push_slice_mut(&mut self, slice: &[T]) -> Range<usize> {
if slice.is_empty() {
return 0..0;
}
let index = self.prepare_contiguous_slice_mut(slice.len());
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
debug_assert!(slice.len() <= bucket_len(bucket, self.bucket_offset) - bucket_index);
let bucket_ptr = self.get_bucket_ptr_mut(bucket);
unsafe { self.clone_slice(bucket_ptr, bucket_index, slice) };
self.len.store(index + slice.len(), Ordering::Release);
index..index + slice.len()
}
unsafe fn clone_slice(&self, bucket_ptr: *mut T, bucket_index: usize, slice: &[T]) {
for (i, item) in slice.iter().enumerate() {
let ptr = unsafe { bucket_ptr.add(bucket_index + i) };
let item: T = item.clone();
unsafe { std::ptr::write(ptr, item) };
}
}
}
impl<T: Copy + Default> AppendVec<T> {
pub fn push_slice_copy(&self, slice: &[T]) -> Range<usize> {
if slice.is_empty() {
return 0..0;
}
let guard = self.write_lock.lock().unwrap();
let (guard, index) = self.prepare_contiguous_slice(slice.len(), guard);
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
debug_assert!(slice.len() <= bucket_len(bucket, self.bucket_offset) - bucket_index);
let bucket_ptr = self.get_bucket_ptr(bucket, &guard);
unsafe { self.copy_slice(bucket_ptr, bucket_index, slice) };
self.len.store(index + slice.len(), Ordering::Release);
drop(guard);
index..index + slice.len()
}
pub fn push_slice_copy_mut(&mut self, slice: &[T]) -> Range<usize> {
if slice.is_empty() {
return 0..0;
}
let index = self.prepare_contiguous_slice_mut(slice.len());
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
debug_assert!(slice.len() <= bucket_len(bucket, self.bucket_offset) - bucket_index);
let bucket_ptr = self.get_bucket_ptr_mut(bucket);
unsafe { self.copy_slice(bucket_ptr, bucket_index, slice) };
self.len.store(index + slice.len(), Ordering::Release);
index..index + slice.len()
}
unsafe fn copy_slice(&self, bucket_ptr: *mut T, bucket_index: usize, slice: &[T]) {
let ptr = unsafe { bucket_ptr.add(bucket_index) };
unsafe { std::ptr::copy_nonoverlapping(slice.as_ptr(), ptr, slice.len()) };
}
}
impl<T> Drop for AppendVec<T> {
fn drop(&mut self) {
let len = self.len.load(Ordering::Acquire);
if len == 0 {
return;
}
let (max_bucket, max_index) = bucketize(len - 1, self.bucket_offset);
for bucket in 0..=max_bucket {
if bucket != 0 && bucket < self.bucket_offset as usize {
continue;
}
let bucket_len = bucket_len(bucket, self.bucket_offset);
let bucket_items = if bucket != max_bucket {
bucket_len
} else {
max_index + 1
};
let ptr: *mut T = self.buckets[bucket].load(Ordering::Relaxed);
let slice: *mut [T] = std::ptr::slice_from_raw_parts_mut(ptr, bucket_items);
unsafe { std::ptr::drop_in_place(slice) };
let vec: Vec<T> = unsafe { Vec::from_raw_parts(ptr, 0, bucket_len) };
drop(vec);
}
}
}
impl<T> Index<usize> for AppendVec<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
assert!(index < self.len.load(Ordering::Acquire));
let (bucket, bucket_index) = bucketize(index, self.bucket_offset);
let bucket_ptr = self.buckets[bucket].load(Ordering::Relaxed) as *const T;
debug_assert_ne!(bucket_ptr, std::ptr::null());
let ptr = unsafe { bucket_ptr.add(bucket_index) };
unsafe { &*ptr }
}
}
impl<T> Index<Range<usize>> for AppendVec<T> {
type Output = [T];
fn index(&self, index: Range<usize>) -> &Self::Output {
if index.start == index.end {
return &[];
}
assert!(index.start <= index.end);
let index_len = index.end - index.start;
assert!(index.end <= self.len.load(Ordering::Acquire));
let (bucket, bucket_index) = bucketize(index.start, self.bucket_offset);
assert!(index_len <= bucket_len(bucket, self.bucket_offset) - bucket_index);
let bucket_ptr = self.buckets[bucket].load(Ordering::Relaxed) as *const T;
debug_assert_ne!(bucket_ptr, std::ptr::null());
let ptr = unsafe { bucket_ptr.add(bucket_index) };
unsafe { std::slice::from_raw_parts(ptr, index_len) }
}
}
pub struct AppendVecIter<'a, T> {
inner: &'a AppendVec<T>,
bucket_offset: u32,
len: usize,
index: usize,
bucket_ptr: *const T,
}
unsafe impl<T: Sync> Send for AppendVecIter<'_, T> {}
unsafe impl<T: Sync> Sync for AppendVecIter<'_, T> {}
impl<'a, T> Iterator for AppendVecIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
if self.index == self.len {
None
} else {
let (bucket, bucket_index) = bucketize(self.index, self.bucket_offset);
self.index += 1;
if bucket_index == 0 {
self.bucket_ptr = self.inner.buckets[bucket].load(Ordering::Relaxed) as *const T;
debug_assert_ne!(self.bucket_ptr, std::ptr::null());
}
let ptr = unsafe { self.bucket_ptr.add(bucket_index) };
unsafe { Some(&*ptr) }
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.len - self.index;
(remaining, Some(remaining))
}
}
impl<T> ExactSizeIterator for AppendVecIter<'_, T> {}
pub struct AppendVecChunksIter<'a, T> {
inner: &'a AppendVec<T>,
bucket_offset: u32,
bucket: usize,
max_bucket: usize,
max_index: usize,
}
unsafe impl<T: Sync> Send for AppendVecChunksIter<'_, T> {}
unsafe impl<T: Sync> Sync for AppendVecChunksIter<'_, T> {}
impl<'a, T> Iterator for AppendVecChunksIter<'a, T> {
type Item = &'a [T];
fn next(&mut self) -> Option<Self::Item> {
if self.bucket > self.max_bucket {
None
} else {
let bucket_len = bucket_len(self.bucket, self.bucket_offset);
let bucket_items = if self.bucket != self.max_bucket {
bucket_len
} else {
self.max_index + 1
};
let bucket_ptr = self.inner.buckets[self.bucket].load(Ordering::Relaxed) as *const T;
debug_assert_ne!(bucket_ptr, std::ptr::null());
self.bucket = if self.bucket == 0 {
self.bucket_offset as usize
} else {
self.bucket + 1
};
unsafe { Some(std::slice::from_raw_parts(bucket_ptr, bucket_items)) }
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
debug_assert!(self.bucket == 0 || self.bucket >= self.bucket_offset as usize);
let adjusted_bucket = if self.bucket == 0 {
0
} else {
self.bucket + 1 - self.bucket_offset as usize
};
debug_assert!(self.max_bucket == 0 || self.max_bucket >= self.bucket_offset as usize);
let adjusted_max_bucket = if self.max_bucket == 0 {
0
} else {
self.max_bucket + 1 - self.bucket_offset as usize
};
let remaining = adjusted_max_bucket + 1 - adjusted_bucket;
(remaining, Some(remaining))
}
}
impl<T> ExactSizeIterator for AppendVecChunksIter<'_, T> {}
const fn bucketize(index: usize, bucket_offset: u32) -> (usize, usize) {
let mut bucket = (usize::BITS - 1).saturating_sub(index.leading_zeros());
if bucket < bucket_offset {
bucket = 0;
}
let bucket_index = if bucket == 0 {
index
} else {
index - (1 << bucket)
};
(bucket as usize, bucket_index)
}
const fn bucket_len(bucket: usize, bucket_offset: u32) -> usize {
if bucket == 0 {
1 << bucket_offset
} else if bucket < bucket_offset as usize {
panic!("Invalid bucket between 0 and bucket_offset");
} else {
1 << bucket
}
}
#[cfg(test)]
mod test {
use super::*;
use std::ops::Deref;
use std::{array, thread};
#[test]
fn test_item_size_log2() {
assert_eq!(AppendVec::<u8>::ITEM_SIZE_LOG2, 0);
assert_eq!(AppendVec::<u16>::ITEM_SIZE_LOG2, 1);
assert_eq!(AppendVec::<u32>::ITEM_SIZE_LOG2, 2);
assert_eq!(AppendVec::<u64>::ITEM_SIZE_LOG2, 3);
assert_eq!(AppendVec::<[u8; 2]>::ITEM_SIZE_LOG2, 1);
assert_eq!(AppendVec::<[u8; 3]>::ITEM_SIZE_LOG2, 2);
assert_eq!(AppendVec::<[u8; 4]>::ITEM_SIZE_LOG2, 2);
assert_eq!(AppendVec::<[u8; 5]>::ITEM_SIZE_LOG2, 3);
assert_eq!(AppendVec::<[u8; 6]>::ITEM_SIZE_LOG2, 3);
assert_eq!(AppendVec::<[u8; 7]>::ITEM_SIZE_LOG2, 3);
}
#[test]
fn test_max_len() {
assert_eq!(AppendVec::<u8>::MAX_LEN, usize::MAX >> 1);
assert_eq!(AppendVec::<u16>::MAX_LEN, usize::MAX >> 2);
assert_eq!(AppendVec::<u32>::MAX_LEN, usize::MAX >> 3);
assert_eq!(AppendVec::<u64>::MAX_LEN, usize::MAX >> 4);
assert_eq!(AppendVec::<[u8; 2]>::MAX_LEN, usize::MAX >> 2);
assert_eq!(AppendVec::<[u8; 3]>::MAX_LEN, usize::MAX >> 3);
assert_eq!(AppendVec::<[u8; 4]>::MAX_LEN, usize::MAX >> 3);
assert_eq!(AppendVec::<[u8; 5]>::MAX_LEN, usize::MAX >> 4);
assert_eq!(AppendVec::<[u8; 6]>::MAX_LEN, usize::MAX >> 4);
assert_eq!(AppendVec::<[u8; 7]>::MAX_LEN, usize::MAX >> 4);
}
#[test]
fn test_bucketize() {
assert_eq!(bucketize(0, 1), (0, 0));
assert_eq!(bucketize(1, 1), (0, 1));
assert_eq!(bucketize(2, 1), (1, 0));
assert_eq!(bucketize(3, 1), (1, 1));
assert_eq!(bucketize(4, 1), (2, 0));
assert_eq!(bucketize(5, 1), (2, 1));
assert_eq!(bucketize(6, 1), (2, 2));
assert_eq!(bucketize(7, 1), (2, 3));
assert_eq!(bucketize(8, 1), (3, 0));
assert_eq!(bucketize(9, 1), (3, 1));
assert_eq!(bucketize(10, 1), (3, 2));
assert_eq!(bucketize(0, 2), (0, 0));
assert_eq!(bucketize(1, 2), (0, 1));
assert_eq!(bucketize(2, 2), (0, 2));
assert_eq!(bucketize(3, 2), (0, 3));
assert_eq!(bucketize(4, 2), (2, 0));
assert_eq!(bucketize(5, 2), (2, 1));
assert_eq!(bucketize(6, 2), (2, 2));
assert_eq!(bucketize(7, 2), (2, 3));
assert_eq!(bucketize(8, 2), (3, 0));
assert_eq!(bucketize(9, 2), (3, 1));
assert_eq!(bucketize(10, 2), (3, 2));
assert_eq!(bucketize(0, 3), (0, 0));
assert_eq!(bucketize(1, 3), (0, 1));
assert_eq!(bucketize(2, 3), (0, 2));
assert_eq!(bucketize(3, 3), (0, 3));
assert_eq!(bucketize(4, 3), (0, 4));
assert_eq!(bucketize(5, 3), (0, 5));
assert_eq!(bucketize(6, 3), (0, 6));
assert_eq!(bucketize(7, 3), (0, 7));
assert_eq!(bucketize(8, 3), (3, 0));
assert_eq!(bucketize(9, 3), (3, 1));
assert_eq!(bucketize(10, 3), (3, 2));
}
#[test]
fn test_bucket_len() {
assert_eq!(bucket_len(0, 1), 2);
assert_eq!(bucket_len(1, 1), 2);
assert_eq!(bucket_len(2, 1), 4);
assert_eq!(bucket_len(3, 1), 8);
assert_eq!(bucket_len(4, 1), 16);
assert_eq!(bucket_len(5, 1), 32);
assert_eq!(bucket_len(0, 2), 4);
assert_eq!(bucket_len(2, 2), 4);
assert_eq!(bucket_len(3, 2), 8);
assert_eq!(bucket_len(4, 2), 16);
assert_eq!(bucket_len(5, 2), 32);
assert_eq!(bucket_len(0, 3), 8);
assert_eq!(bucket_len(3, 3), 8);
assert_eq!(bucket_len(4, 3), 16);
assert_eq!(bucket_len(5, 3), 32);
}
#[test]
#[should_panic(expected = "Invalid bucket between 0 and bucket_offset")]
fn test_invalid_bucket_len() {
let _ = bucket_len(1, 2);
}
#[test]
#[should_panic(expected = "AppendVec: requested capacity is too large for the given type")]
fn test_with_overlarge_capacity() {
let _ = AppendVec::<u8>::with_capacity(usize::MAX);
}
#[test]
fn test_push_index() {
let v = AppendVec::new();
for i in 0..100 {
assert_eq!(v.push(i), i);
}
for i in 0..100 {
assert_eq!(v[i], i);
}
}
#[test]
fn test_push_mut_index() {
let mut v = AppendVec::new();
for i in 0..100 {
assert_eq!(v.push_mut(i), i);
}
for i in 0..100 {
assert_eq!(v[i], i);
}
}
#[test]
fn test_push_slice_index() {
let v = AppendVec::new();
for len in 0..10 {
let mut prev = 0..0;
for i in 0..100 {
let data = vec![i; len];
let index = v.push_slice(&data);
assert!(index.start >= prev.end);
assert_eq!(index.end - index.start, len);
prev = index.clone();
assert_eq!(&v[index], &data);
}
}
}
#[test]
fn test_push_slice_mut_index() {
let mut v = AppendVec::new();
for len in 0..10 {
let mut prev = 0..0;
for i in 0..100 {
let data = vec![i; len];
let index = v.push_slice_mut(&data);
assert!(index.start >= prev.end);
assert_eq!(index.end - index.start, len);
prev = index.clone();
assert_eq!(&v[index], &data);
}
}
}
#[test]
fn test_push_slice_padding() {
for len in 1..100 {
let v = AppendVec::new();
let range = v.push_slice(&vec![42; len]);
assert_eq!(range.end - range.start, len);
let bucket_start = if len <= 2 {
0
} else {
let bucket = (len - 1).ilog2() + 1;
1 << bucket
};
assert_eq!(range.start, bucket_start);
for i in 0..range.start {
assert_eq!(v[i], 0);
}
}
}
const NUM_READERS: usize = 4;
const NUM_WRITERS: usize = 4;
#[cfg(not(miri))]
const NUM_ITEMS: usize = 100_000;
#[cfg(miri)]
const NUM_ITEMS: usize = 100;
#[test]
fn test_push_index_concurrent_reads() {
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - 1;
assert_eq!(*v[last].deref(), last);
if len == NUM_ITEMS {
break;
}
}
}
});
}
s.spawn(|| {
for j in 0..NUM_ITEMS {
assert_eq!(v.push(Box::new(j)), j);
}
});
});
}
#[test]
fn test_push_index_concurrent_writes() {
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - 1;
assert!(*v[last].deref() <= last);
if len == NUM_WRITERS * NUM_ITEMS {
break;
}
}
}
});
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
assert!(v.push(Box::new(j)) >= j);
}
});
}
});
}
#[test]
fn test_push_index_concurrent_readwrites() {
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - 1;
assert!(*v[last].deref() <= last);
if len == NUM_WRITERS * NUM_ITEMS {
break;
}
}
}
});
}
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
assert!(v.push(Box::new(j)) >= j);
}
});
}
});
}
#[test]
fn test_iter() {
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let mut iter = v.iter();
let mut remaining_len = iter.len();
assert_eq!(iter.size_hint(), (remaining_len, Some(remaining_len)));
let mut i = 0;
while let Some(x) = iter.next() {
assert_eq!(i, **x);
i += 1;
remaining_len -= 1;
assert_eq!(iter.size_hint(), (remaining_len, Some(remaining_len)));
}
if i == NUM_ITEMS {
break;
}
}
});
}
s.spawn(|| {
for j in 0..NUM_ITEMS {
assert_eq!(v.push(Box::new(j)), j);
}
});
});
}
#[test]
fn test_iter_with_capacity() {
for capacity in [0, NUM_ITEMS / 3] {
let v: AppendVec<Box<usize>> = AppendVec::with_capacity(capacity);
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let mut iter = v.iter();
let mut remaining_len = iter.len();
assert_eq!(iter.size_hint(), (remaining_len, Some(remaining_len)));
let mut i = 0;
while let Some(x) = iter.next() {
assert_eq!(i, **x);
i += 1;
remaining_len -= 1;
assert_eq!(iter.size_hint(), (remaining_len, Some(remaining_len)));
}
if i == NUM_ITEMS {
break;
}
}
});
}
s.spawn(|| {
for j in 0..NUM_ITEMS {
assert_eq!(v.push(Box::new(j)), j);
}
});
});
}
}
#[test]
fn test_iter_chunks() {
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let mut iter = v.iter_chunks();
let mut remaining_chunks = iter.len();
assert_eq!(iter.size_hint(), (remaining_chunks, Some(remaining_chunks)));
let mut i = 0;
while let Some(chunk) = iter.next() {
for x in chunk {
assert_eq!(i, **x);
i += 1;
}
remaining_chunks -= 1;
assert_eq!(
iter.size_hint(),
(remaining_chunks, Some(remaining_chunks))
);
}
if i == NUM_ITEMS {
break;
}
}
});
}
s.spawn(|| {
for j in 0..NUM_ITEMS {
assert_eq!(v.push(Box::new(j)), j);
}
});
});
}
#[test]
fn test_iter_chunks_with_capacity() {
for capacity in [0, NUM_ITEMS / 3] {
let v: AppendVec<Box<usize>> = AppendVec::with_capacity(capacity);
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let mut iter = v.iter_chunks();
let mut remaining_chunks = iter.len();
assert_eq!(
iter.size_hint(),
(remaining_chunks, Some(remaining_chunks))
);
let mut i = 0;
while let Some(chunk) = iter.next() {
for x in chunk {
assert_eq!(i, **x);
i += 1;
}
remaining_chunks -= 1;
assert_eq!(
iter.size_hint(),
(remaining_chunks, Some(remaining_chunks))
);
}
if i == NUM_ITEMS {
break;
}
}
});
}
s.spawn(|| {
for j in 0..NUM_ITEMS {
assert_eq!(v.push(Box::new(j)), j);
}
});
});
}
}
#[test]
fn test_push_slice_index_concurrent_reads() {
const SLICE_LEN: usize = 7;
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - SLICE_LEN;
let slice = &v[last..len];
assert!(*slice[0] * SLICE_LEN <= last);
for i in 1..SLICE_LEN {
assert_eq!(slice[i], slice[0]);
}
if len >= SLICE_LEN * NUM_ITEMS {
break;
}
}
}
});
}
s.spawn(|| {
for j in 0..NUM_ITEMS {
let slice: [Box<usize>; SLICE_LEN] = array::from_fn(|_| Box::new(j));
assert!(v.push_slice(&slice).start >= SLICE_LEN * j);
}
});
});
}
#[test]
fn test_push_slice_index_concurrent_writes() {
const SLICE_LEN: usize = 7;
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - SLICE_LEN;
let slice = &v[last..len];
assert!(*slice[0] * SLICE_LEN <= last);
for i in 1..SLICE_LEN {
assert_eq!(slice[i], slice[0]);
}
if len >= NUM_WRITERS * SLICE_LEN * NUM_ITEMS {
break;
}
}
}
});
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
let slice: [Box<usize>; SLICE_LEN] = array::from_fn(|_| Box::new(j));
assert!(v.push_slice(&slice).start >= SLICE_LEN * j);
}
});
}
});
}
#[test]
fn test_push_slice_index_concurrent_readwrites() {
const SLICE_LEN: usize = 7;
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - SLICE_LEN;
let slice = &v[last..len];
assert!(*slice[0] * SLICE_LEN <= last);
for i in 1..SLICE_LEN {
assert_eq!(slice[i], slice[0]);
}
if len >= NUM_WRITERS * SLICE_LEN * NUM_ITEMS {
break;
}
}
}
});
}
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
let slice: [Box<usize>; SLICE_LEN] = array::from_fn(|_| Box::new(j));
assert!(v.push_slice(&slice).start >= SLICE_LEN * j);
}
});
}
});
}
#[test]
fn test_push_slice_with_little_capacity() {
const SLICE_LEN: usize = 7;
for capacity in [0, SLICE_LEN] {
let v: AppendVec<Box<usize>> = AppendVec::with_capacity(capacity);
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - SLICE_LEN;
let slice = &v[last..len];
assert!(*slice[0] * SLICE_LEN <= last);
for i in 1..SLICE_LEN {
assert_eq!(slice[i], slice[0]);
}
if len >= NUM_WRITERS * SLICE_LEN * NUM_ITEMS {
break;
}
}
}
});
}
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
let slice: [Box<usize>; SLICE_LEN] = array::from_fn(|_| Box::new(j));
assert!(v.push_slice(&slice).start >= SLICE_LEN * j);
}
});
}
});
}
}
#[test]
fn test_push_slice_with_enough_capacity() {
const SLICE_LEN: usize = 7;
let v: AppendVec<Box<usize>> =
AppendVec::with_capacity(NUM_WRITERS * SLICE_LEN * NUM_ITEMS);
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - SLICE_LEN;
let slice = &v[last..len];
assert!(*slice[0] * SLICE_LEN <= last);
for i in 1..SLICE_LEN {
assert_eq!(slice[i], slice[0]);
}
if len == NUM_WRITERS * SLICE_LEN * NUM_ITEMS {
break;
}
}
}
});
}
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
let slice: [Box<usize>; SLICE_LEN] = array::from_fn(|_| Box::new(j));
assert!(v.push_slice(&slice).start >= SLICE_LEN * j);
}
});
}
});
assert_eq!(v.len(), NUM_WRITERS * SLICE_LEN * NUM_ITEMS);
}
#[test]
fn test_push_owned_slice_with_little_capacity() {
const SLICE_LEN: usize = 7;
for capacity in [0, SLICE_LEN] {
let v: AppendVec<Box<usize>> = AppendVec::with_capacity(capacity);
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - SLICE_LEN;
let slice = &v[last..len];
assert!(*slice[0] * SLICE_LEN <= last);
for i in 1..SLICE_LEN {
assert_eq!(slice[i], slice[0]);
}
if len >= NUM_WRITERS * SLICE_LEN * NUM_ITEMS {
break;
}
}
}
});
}
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
let vec = vec![Box::new(j); SLICE_LEN];
assert!(v.push_owned_slice(vec).start >= SLICE_LEN * j);
}
});
}
});
}
}
#[test]
fn test_push_owned_slice_with_enough_capacity() {
const SLICE_LEN: usize = 7;
let v: AppendVec<Box<usize>> =
AppendVec::with_capacity(NUM_WRITERS * SLICE_LEN * NUM_ITEMS);
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - SLICE_LEN;
let slice = &v[last..len];
assert!(*slice[0] * SLICE_LEN <= last);
for i in 1..SLICE_LEN {
assert_eq!(slice[i], slice[0]);
}
if len == NUM_WRITERS * SLICE_LEN * NUM_ITEMS {
break;
}
}
}
});
}
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
let vec = vec![Box::new(j); SLICE_LEN];
assert!(v.push_owned_slice(vec).start >= SLICE_LEN * j);
}
});
}
});
assert_eq!(v.len(), NUM_WRITERS * SLICE_LEN * NUM_ITEMS);
}
#[test]
fn test_push_array_with_little_capacity() {
const SLICE_LEN: usize = 7;
for capacity in [0, SLICE_LEN] {
let v: AppendVec<Box<usize>> = AppendVec::with_capacity(capacity);
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - SLICE_LEN;
let slice = &v[last..len];
assert!(*slice[0] * SLICE_LEN <= last);
for i in 1..SLICE_LEN {
assert_eq!(slice[i], slice[0]);
}
if len >= NUM_WRITERS * SLICE_LEN * NUM_ITEMS {
break;
}
}
}
});
}
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
let array: [Box<usize>; SLICE_LEN] = array::from_fn(|_| Box::new(j));
assert!(v.push_array(array).start >= SLICE_LEN * j);
}
});
}
});
}
}
#[test]
fn test_push_array_with_enough_capacity() {
const SLICE_LEN: usize = 7;
let v: AppendVec<Box<usize>> =
AppendVec::with_capacity(NUM_WRITERS * SLICE_LEN * NUM_ITEMS);
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - SLICE_LEN;
let slice = &v[last..len];
assert!(*slice[0] * SLICE_LEN <= last);
for i in 1..SLICE_LEN {
assert_eq!(slice[i], slice[0]);
}
if len == NUM_WRITERS * SLICE_LEN * NUM_ITEMS {
break;
}
}
}
});
}
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
let array: [Box<usize>; SLICE_LEN] = array::from_fn(|_| Box::new(j));
assert!(v.push_array(array).start >= SLICE_LEN * j);
}
});
}
});
assert_eq!(v.len(), NUM_WRITERS * SLICE_LEN * NUM_ITEMS);
}
#[test]
fn test_get_unchecked_concurrent_reads() {
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - 1;
let x = unsafe { v.get_unchecked(last) };
assert_eq!(*x.deref(), last);
if len == NUM_ITEMS {
break;
}
}
}
});
}
s.spawn(|| {
for j in 0..NUM_ITEMS {
assert_eq!(v.push(Box::new(j)), j);
}
});
});
}
#[test]
fn test_get_unchecked_concurrent_writes() {
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - 1;
let x = unsafe { v.get_unchecked(last) };
assert!(*x.deref() <= last);
if len == NUM_WRITERS * NUM_ITEMS {
break;
}
}
}
});
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
assert!(v.push(Box::new(j)) >= j);
}
});
}
});
}
#[test]
fn test_get_unchecked_concurrent_readwrites() {
let v: AppendVec<Box<usize>> = AppendVec::new();
thread::scope(|s| {
for _ in 0..NUM_READERS {
s.spawn(|| {
loop {
let len = v.len();
if len > 0 {
let last = len - 1;
let x = unsafe { v.get_unchecked(last) };
assert!(*x.deref() <= last);
if len == NUM_WRITERS * NUM_ITEMS {
break;
}
}
}
});
}
for _ in 0..NUM_WRITERS {
s.spawn(|| {
for j in 0..NUM_ITEMS {
assert!(v.push(Box::new(j)) >= j);
}
});
}
});
}
}