use std::marker::PhantomData;
use std::sync::Arc;
pub const CHUNK_SIZE: usize = 2048;
#[derive(Clone, Debug)]
pub struct ChunkedVec<T> {
chunks: Vec<Arc<[T]>>,
tail: Arc<Vec<T>>,
len: usize,
_marker: PhantomData<T>,
}
impl<T: Clone> ChunkedVec<T> {
#[must_use]
pub fn new() -> Self {
Self {
chunks: Vec::new(),
tail: Arc::new(Vec::new()),
len: 0,
_marker: PhantomData,
}
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
chunks: Vec::with_capacity(capacity.div_ceil(CHUNK_SIZE)),
tail: Arc::new(Vec::new()),
len: 0,
_marker: PhantomData,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn get(&self, index: usize) -> Option<&T> {
if index >= self.len {
return None;
}
let (chunk_index, offset) = locate(index);
if chunk_index < self.chunks.len() {
self.chunks[chunk_index].get(offset)
} else {
self.tail.get(offset)
}
}
pub fn push(&mut self, value: T) {
let tail = Arc::make_mut(&mut self.tail);
if tail.capacity() == 0 {
tail.reserve(CHUNK_SIZE);
}
tail.push(value);
self.len += 1;
if tail.len() == CHUNK_SIZE {
let frozen = std::mem::take(tail);
self.chunks.push(Arc::from(frozen));
}
}
pub fn set(&mut self, index: usize, value: T) {
assert!(
index < self.len,
"ChunkedVec::set index {index} out of bounds for len {}",
self.len
);
let (chunk_index, offset) = locate(index);
if chunk_index < self.chunks.len() {
let chunk = Arc::make_mut(&mut self.chunks[chunk_index]);
chunk[offset] = value;
} else {
Arc::make_mut(&mut self.tail)[offset] = value;
}
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.chunks
.iter()
.flat_map(|chunk| chunk.iter())
.chain(self.tail.iter())
}
#[cfg(test)]
pub(crate) fn chunk_count(&self) -> usize {
self.chunks.len() + if self.tail.is_empty() { 0 } else { 1 }
}
#[cfg(test)]
pub(crate) fn chunk_capacity(&self) -> usize {
self.chunks.capacity() + 1
}
#[cfg(test)]
pub(crate) fn chunk_arc(&self, index: usize) -> Arc<[T]> {
if index < self.chunks.len() {
Arc::clone(&self.chunks[index])
} else {
Arc::from(self.tail.as_slice())
}
}
#[cfg(test)]
pub(crate) fn slow_equals(&self, other: &Self) -> bool
where
T: PartialEq,
{
self.iter().eq(other.iter())
}
}
impl<T: Clone> Default for ChunkedVec<T> {
fn default() -> Self {
Self::new()
}
}
fn locate(index: usize) -> (usize, usize) {
(index / CHUNK_SIZE, index % CHUNK_SIZE)
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::*;
#[test]
fn new_is_empty() {
let vec = ChunkedVec::<u64>::new();
assert_eq!(vec.len(), 0);
assert!(vec.is_empty());
assert_eq!(vec.chunk_count(), 0);
}
#[test]
fn push_grows_and_get_returns_pushed_values() {
let mut vec = ChunkedVec::new();
for value in 0..5000 {
vec.push(value);
}
assert_eq!(vec.len(), 5000);
for value in 0..5000 {
assert_eq!(vec.get(value), Some(&value));
}
assert_eq!(vec.chunk_count(), 3);
}
#[test]
fn push_does_not_clone_tail_per_call() {
let mut vec = ChunkedVec::new();
for value in 0..CHUNK_SIZE {
vec.push(value);
}
assert_eq!(vec.chunks.len(), 1);
let first_chunk_handle = Arc::clone(&vec.chunks[0]);
for value in 0..100 {
vec.push(CHUNK_SIZE + value);
}
assert_eq!(Arc::strong_count(&first_chunk_handle), 2);
assert_eq!(vec.len(), CHUNK_SIZE + 100);
}
#[test]
fn set_replaces_value_at_index() {
let mut vec = ChunkedVec::new();
vec.push(1);
vec.push(2);
vec.set(1, 9);
assert_eq!(vec.get(1), Some(&9));
}
#[test]
fn set_only_clones_affected_chunk() {
let mut original = ChunkedVec::new();
for value in 0..4096 {
original.push(value);
}
let mut cloned = original.clone();
assert!(original.slow_equals(&cloned));
let original_chunk_0 = original.chunk_arc(0);
let original_chunk_1 = original.chunk_arc(1);
cloned.set(CHUNK_SIZE + 1, 99);
assert!(!original.slow_equals(&cloned));
assert_eq!(Arc::strong_count(&original_chunk_0), 3);
assert_eq!(Arc::strong_count(&original_chunk_1), 2);
assert_eq!(original.get(CHUNK_SIZE + 1), Some(&(CHUNK_SIZE + 1)));
assert_eq!(cloned.get(CHUNK_SIZE + 1), Some(&99));
}
#[test]
fn set_in_tail_does_not_touch_frozen_chunks() {
let mut vec = ChunkedVec::new();
for value in 0..CHUNK_SIZE + 50 {
vec.push(value);
}
let frozen_handle = Arc::clone(&vec.chunks[0]);
vec.set(CHUNK_SIZE + 10, 999);
assert_eq!(Arc::strong_count(&frozen_handle), 2);
assert_eq!(vec.get(CHUNK_SIZE + 10), Some(&999));
}
#[test]
fn iter_yields_all_values_in_order() {
let mut vec = ChunkedVec::new();
for value in 0..4096 {
vec.push(value);
}
assert_eq!(
vec.iter().copied().collect::<Vec<_>>(),
(0..4096).collect::<Vec<_>>()
);
}
#[test]
fn iter_includes_tail() {
let mut vec = ChunkedVec::new();
for value in 0..CHUNK_SIZE + 5 {
vec.push(value);
}
assert_eq!(
vec.iter().copied().collect::<Vec<_>>(),
(0..CHUNK_SIZE + 5).collect::<Vec<_>>()
);
}
#[test]
fn with_capacity_does_not_overallocate_entries() {
let vec = ChunkedVec::<u64>::with_capacity(CHUNK_SIZE * 3 + 1);
assert_eq!(vec.len(), 0);
assert_eq!(vec.chunk_count(), 0);
assert!(vec.chunk_capacity() >= 4);
}
#[test]
fn get_out_of_bounds_returns_none() {
let mut vec = ChunkedVec::new();
vec.push(1);
assert_eq!(vec.get(1), None);
}
#[test]
#[should_panic(expected = "ChunkedVec::set index 1 out of bounds for len 1")]
fn set_out_of_bounds_panics_clearly() {
let mut vec = ChunkedVec::new();
vec.push(1);
vec.set(1, 2);
}
#[test]
fn clone_shares_tail_without_copying() {
let mut original = ChunkedVec::new();
for value in 0..100 {
original.push(value);
}
let cloned = original.clone();
assert!(Arc::ptr_eq(&original.tail, &cloned.tail));
assert_eq!(Arc::strong_count(&original.tail), 2);
assert!(original.slow_equals(&cloned));
}
#[test]
fn clone_then_push_isolates_original_snapshot() {
let mut original = ChunkedVec::new();
for value in 0..10 {
original.push(value);
}
let snapshot_tail = Arc::clone(&original.tail);
let mut cloned = original.clone();
cloned.push(999);
assert_eq!(original.len(), 10);
assert_eq!(original.get(10), None);
for value in 0..10 {
assert_eq!(original.get(value), Some(&value));
}
assert_eq!(cloned.get(10), Some(&999));
assert!(!Arc::ptr_eq(&original.tail, &cloned.tail));
assert_eq!(Arc::strong_count(&snapshot_tail), 2);
}
#[test]
fn clone_then_set_in_tail_isolates_original_snapshot() {
let mut original = ChunkedVec::new();
for value in 0..10 {
original.push(value);
}
let mut cloned = original.clone();
cloned.set(3, 777);
assert_eq!(original.get(3), Some(&3));
assert_eq!(cloned.get(3), Some(&777));
assert!(!Arc::ptr_eq(&original.tail, &cloned.tail));
}
#[test]
fn first_write_makes_tail_unique_then_reuses_buffer() {
let mut original = ChunkedVec::new();
for value in 0..10 {
original.push(value);
}
let mut cloned = original.clone();
cloned.push(100);
let unique_tail_ptr = Arc::as_ptr(&cloned.tail);
cloned.push(101);
cloned.set(0, 42);
assert_eq!(Arc::as_ptr(&cloned.tail), unique_tail_ptr);
assert_eq!(Arc::strong_count(&cloned.tail), 1);
assert_eq!(cloned.get(0), Some(&42));
assert_eq!(original.get(0), Some(&0));
}
#[test]
fn freeze_under_share_preserves_both_columns() {
let mut original = ChunkedVec::new();
for value in 0..CHUNK_SIZE - 1 {
original.push(value);
}
let mut cloned = original.clone();
cloned.push(CHUNK_SIZE - 1); for value in CHUNK_SIZE..CHUNK_SIZE + 5 {
cloned.push(value);
}
assert_eq!(original.len(), CHUNK_SIZE - 1);
assert_eq!(original.chunks.len(), 0);
assert_eq!(original.tail.len(), CHUNK_SIZE - 1);
for value in 0..CHUNK_SIZE - 1 {
assert_eq!(original.get(value), Some(&value));
}
assert_eq!(cloned.len(), CHUNK_SIZE + 5);
assert_eq!(cloned.chunks.len(), 1);
assert_eq!(cloned.tail.len(), 5);
for value in 0..CHUNK_SIZE + 5 {
assert_eq!(cloned.get(value), Some(&value));
}
}
proptest! {
#[test]
fn random_push_set_sequence_preserves_latest_values(ops in proptest::collection::vec((any::<bool>(), 0_usize..128, any::<u16>()), 1..256)) {
let mut vec = ChunkedVec::new();
let mut expected = Vec::new();
for (set_existing, index, value) in ops {
if set_existing && !expected.is_empty() {
let idx = index % expected.len();
vec.set(idx, value);
expected[idx] = value;
} else {
vec.push(value);
expected.push(value);
}
prop_assert_eq!(vec.len(), expected.len());
for (idx, expected_value) in expected.iter().enumerate() {
prop_assert_eq!(vec.get(idx), Some(expected_value));
}
}
}
#[test]
fn clone_then_mutate_never_disturbs_snapshot(
seed_ops in proptest::collection::vec((any::<bool>(), 0_usize..4096, any::<u16>()), 1..512),
post_ops in proptest::collection::vec((any::<bool>(), 0_usize..4096, any::<u16>()), 1..512),
) {
let mut live = ChunkedVec::new();
let mut model = Vec::new();
for (set_existing, index, value) in seed_ops {
if set_existing && !model.is_empty() {
let idx = index % model.len();
live.set(idx, value);
model[idx] = value;
} else {
live.push(value);
model.push(value);
}
}
let snapshot = live.clone();
let snapshot_model = model.clone();
for (set_existing, index, value) in post_ops {
if set_existing && !model.is_empty() {
let idx = index % model.len();
live.set(idx, value);
model[idx] = value;
} else {
live.push(value);
model.push(value);
}
}
prop_assert_eq!(snapshot.len(), snapshot_model.len());
for (idx, expected_value) in snapshot_model.iter().enumerate() {
prop_assert_eq!(snapshot.get(idx), Some(expected_value));
}
prop_assert_eq!(snapshot.get(snapshot_model.len()), None);
prop_assert_eq!(live.len(), model.len());
for (idx, expected_value) in model.iter().enumerate() {
prop_assert_eq!(live.get(idx), Some(expected_value));
}
}
}
}