#[cfg(feature = "concurrency")]
use parking_lot::RwLock;
use std::iter::FusedIterator;
use crate::ds::slot_arena::{SlotArena, SlotId};
#[derive(Debug, Clone)]
#[repr(C)]
struct Node<T> {
prev: Option<SlotId>,
next: Option<SlotId>,
epoch: u64,
value: T,
}
#[derive(Debug, Clone)]
pub struct IntrusiveList<T> {
arena: SlotArena<Node<T>>,
head: Option<SlotId>,
tail: Option<SlotId>,
}
impl<T> IntrusiveList<T> {
pub fn new() -> Self {
Self {
arena: SlotArena::new(),
head: None,
tail: None,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
arena: SlotArena::with_capacity(capacity),
head: None,
tail: None,
}
}
#[inline]
pub fn len(&self) -> usize {
self.arena.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.arena.is_empty()
}
#[inline]
pub fn contains(&self, id: SlotId) -> bool {
self.arena.contains(id)
}
#[inline]
pub fn front(&self) -> Option<&T> {
self.head
.and_then(|id| self.arena.get(id).map(|node| &node.value))
}
#[inline]
pub fn front_id(&self) -> Option<SlotId> {
self.head
}
#[inline]
pub fn back(&self) -> Option<&T> {
self.tail
.and_then(|id| self.arena.get(id).map(|node| &node.value))
}
#[inline]
pub fn back_id(&self) -> Option<SlotId> {
self.tail
}
pub fn iter(&self) -> IntrusiveListIter<'_, T> {
IntrusiveListIter {
list: self,
current: self.head,
}
}
pub fn iter_ids(&self) -> IntrusiveListIdIter<'_, T> {
IntrusiveListIdIter {
list: self,
current: self.head,
}
}
pub fn iter_entries(&self) -> IntrusiveListEntryIter<'_, T> {
IntrusiveListEntryIter {
list: self,
current: self.head,
}
}
#[inline]
pub fn get(&self, id: SlotId) -> Option<&T> {
self.arena.get(id).map(|node| &node.value)
}
#[inline]
pub fn get_mut(&mut self, id: SlotId) -> Option<&mut T> {
self.arena.get_mut(id).map(|node| &mut node.value)
}
#[inline]
pub fn push_front(&mut self, value: T) -> SlotId {
self.push_front_with_epoch(value, 0)
}
#[inline]
pub fn push_front_with_epoch(&mut self, value: T, epoch: u64) -> SlotId {
let id = self.arena.insert(Node {
value,
prev: None,
next: self.head,
epoch,
});
if let Some(head) = self.head {
if let Some(node) = self.arena.get_mut(head) {
node.prev = Some(id);
}
} else {
self.tail = Some(id);
}
self.head = Some(id);
id
}
#[inline]
pub fn push_back(&mut self, value: T) -> SlotId {
self.push_back_with_epoch(value, 0)
}
#[inline]
pub fn push_back_with_epoch(&mut self, value: T, epoch: u64) -> SlotId {
let id = self.arena.insert(Node {
value,
prev: self.tail,
next: None,
epoch,
});
if let Some(tail) = self.tail {
if let Some(node) = self.arena.get_mut(tail) {
node.next = Some(id);
}
} else {
self.head = Some(id);
}
self.tail = Some(id);
id
}
pub fn epoch(&self, id: SlotId) -> Option<u64> {
self.arena.get(id).map(|node| node.epoch)
}
pub fn set_epoch(&mut self, id: SlotId, epoch: u64) -> bool {
if let Some(node) = self.arena.get_mut(id) {
node.epoch = epoch;
true
} else {
false
}
}
#[inline]
pub fn pop_front(&mut self) -> Option<T> {
let id = self.head?;
self.detach(id)?;
self.arena.remove(id).map(|node| node.value)
}
#[inline]
pub fn pop_back(&mut self) -> Option<T> {
let id = self.tail?;
self.detach(id)?;
self.arena.remove(id).map(|node| node.value)
}
#[inline]
pub fn remove(&mut self, id: SlotId) -> Option<T> {
self.detach(id)?;
self.arena.remove(id).map(|node| node.value)
}
#[inline]
pub fn move_to_front(&mut self, id: SlotId) -> bool {
if !self.arena.contains(id) {
return false;
}
if Some(id) == self.head {
return true;
}
self.detach(id);
self.attach_front(id);
true
}
#[inline]
pub fn move_to_back(&mut self, id: SlotId) -> bool {
if !self.arena.contains(id) {
return false;
}
if Some(id) == self.tail {
return true;
}
self.detach(id);
self.attach_back(id);
true
}
pub fn clear(&mut self) {
self.arena.clear();
self.head = None;
self.tail = None;
}
pub fn clear_shrink(&mut self) {
self.clear();
self.arena.shrink_to_fit();
}
pub fn approx_bytes(&self) -> usize {
std::mem::size_of::<Self>() + self.arena.approx_bytes()
- std::mem::size_of::<SlotArena<Node<T>>>()
}
#[cfg(any(test, debug_assertions))]
#[doc(hidden)]
pub fn debug_snapshot_ids(&self) -> Vec<SlotId> {
self.iter_ids().collect()
}
#[cfg(any(test, debug_assertions))]
#[doc(hidden)]
pub fn debug_snapshot_ids_sorted(&self) -> Vec<SlotId> {
let mut ids: Vec<_> = self.iter_ids().collect();
ids.sort_by_key(|id| id.index());
ids
}
#[inline]
fn detach(&mut self, id: SlotId) -> Option<()> {
let (prev, next) = {
let node = self.arena.get(id)?;
(node.prev, node.next)
};
if let Some(prev_id) = prev {
if let Some(prev_node) = self.arena.get_mut(prev_id) {
prev_node.next = next;
}
} else {
self.head = next;
}
if let Some(next_id) = next {
if let Some(next_node) = self.arena.get_mut(next_id) {
next_node.prev = prev;
}
} else {
self.tail = prev;
}
if let Some(node) = self.arena.get_mut(id) {
node.prev = None;
node.next = None;
}
Some(())
}
#[inline]
fn attach_front(&mut self, id: SlotId) -> Option<()> {
let old_head = self.head;
if let Some(node) = self.arena.get_mut(id) {
node.prev = None;
node.next = old_head;
} else {
return None;
}
if let Some(old_head) = old_head {
if let Some(head_node) = self.arena.get_mut(old_head) {
head_node.prev = Some(id);
}
} else {
self.tail = Some(id);
}
self.head = Some(id);
Some(())
}
#[inline]
fn attach_back(&mut self, id: SlotId) -> Option<()> {
let old_tail = self.tail;
if let Some(node) = self.arena.get_mut(id) {
node.next = None;
node.prev = old_tail;
} else {
return None;
}
if let Some(old_tail) = old_tail {
if let Some(tail_node) = self.arena.get_mut(old_tail) {
tail_node.next = Some(id);
}
} else {
self.head = Some(id);
}
self.tail = Some(id);
Some(())
}
#[cfg(any(test, debug_assertions))]
#[doc(hidden)]
pub fn debug_validate_invariants(&self) {
if self.head.is_none() || self.tail.is_none() {
assert!(self.head.is_none());
assert!(self.tail.is_none());
assert_eq!(self.len(), 0);
return;
}
let mut seen = std::collections::HashSet::new();
let mut count = 0usize;
let mut current = self.head;
let mut prev = None;
while let Some(id) = current {
assert!(seen.insert(id));
let node = self.arena.get(id).expect("node missing");
assert_eq!(node.prev, prev);
if let Some(next_id) = node.next {
let next_node = self.arena.get(next_id).expect("next node missing");
assert_eq!(next_node.prev, Some(id));
} else {
assert_eq!(self.tail, Some(id));
}
prev = Some(id);
current = node.next;
count += 1;
assert!(count <= self.len());
}
assert_eq!(count, self.len());
assert_eq!(self.arena.len(), self.len());
}
}
#[derive(Debug)]
pub struct IntrusiveListIter<'a, T> {
list: &'a IntrusiveList<T>,
current: Option<SlotId>,
}
impl<'a, T> Iterator for IntrusiveListIter<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let id = self.current?;
let node = self.list.arena.get(id)?;
self.current = node.next;
Some(&node.value)
}
}
impl<T> FusedIterator for IntrusiveListIter<'_, T> {}
#[derive(Debug)]
pub struct IntrusiveListIdIter<'a, T> {
list: &'a IntrusiveList<T>,
current: Option<SlotId>,
}
impl<'a, T> Iterator for IntrusiveListIdIter<'a, T> {
type Item = SlotId;
fn next(&mut self) -> Option<Self::Item> {
let id = self.current?;
let node = self.list.arena.get(id)?;
self.current = node.next;
Some(id)
}
}
impl<T> FusedIterator for IntrusiveListIdIter<'_, T> {}
#[derive(Debug)]
pub struct IntrusiveListEntryIter<'a, T> {
list: &'a IntrusiveList<T>,
current: Option<SlotId>,
}
impl<'a, T> Iterator for IntrusiveListEntryIter<'a, T> {
type Item = (SlotId, &'a T);
fn next(&mut self) -> Option<Self::Item> {
let id = self.current?;
let node = self.list.arena.get(id)?;
self.current = node.next;
Some((id, &node.value))
}
}
impl<T> FusedIterator for IntrusiveListEntryIter<'_, T> {}
impl<T> Default for IntrusiveList<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Extend<T> for IntrusiveList<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
for value in iter {
self.push_back(value);
}
}
}
impl<T> FromIterator<T> for IntrusiveList<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut list = Self::new();
list.extend(iter);
list
}
}
impl<'a, T> IntoIterator for &'a IntrusiveList<T> {
type Item = &'a T;
type IntoIter = IntrusiveListIter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Debug)]
pub struct IntrusiveListIntoIter<T> {
list: IntrusiveList<T>,
}
impl<T> Iterator for IntrusiveListIntoIter<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
self.list.pop_front()
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.list.len();
(len, Some(len))
}
}
impl<T> ExactSizeIterator for IntrusiveListIntoIter<T> {}
impl<T> FusedIterator for IntrusiveListIntoIter<T> {}
impl<T> IntoIterator for IntrusiveList<T> {
type Item = T;
type IntoIter = IntrusiveListIntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
IntrusiveListIntoIter { list: self }
}
}
#[cfg(feature = "concurrency")]
#[derive(Debug)]
pub struct ConcurrentIntrusiveList<T> {
inner: RwLock<IntrusiveList<T>>,
}
#[cfg(feature = "concurrency")]
impl<T> ConcurrentIntrusiveList<T> {
pub fn new() -> Self {
Self {
inner: RwLock::new(IntrusiveList::new()),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: RwLock::new(IntrusiveList::with_capacity(capacity)),
}
}
pub fn len(&self) -> usize {
let list = self.inner.read();
list.len()
}
pub fn is_empty(&self) -> bool {
let list = self.inner.read();
list.is_empty()
}
pub fn contains(&self, id: SlotId) -> bool {
let list = self.inner.read();
list.contains(id)
}
pub fn push_front(&self, value: T) -> SlotId {
let mut list = self.inner.write();
list.push_front(value)
}
pub fn try_push_front(&self, value: T) -> Option<SlotId> {
let mut list = self.inner.try_write()?;
Some(list.push_front(value))
}
pub fn push_front_with_epoch(&self, value: T, epoch: u64) -> SlotId {
let mut list = self.inner.write();
list.push_front_with_epoch(value, epoch)
}
pub fn try_push_front_with_epoch(&self, value: T, epoch: u64) -> Option<SlotId> {
let mut list = self.inner.try_write()?;
Some(list.push_front_with_epoch(value, epoch))
}
pub fn push_back(&self, value: T) -> SlotId {
let mut list = self.inner.write();
list.push_back(value)
}
pub fn try_push_back(&self, value: T) -> Option<SlotId> {
let mut list = self.inner.try_write()?;
Some(list.push_back(value))
}
pub fn push_back_with_epoch(&self, value: T, epoch: u64) -> SlotId {
let mut list = self.inner.write();
list.push_back_with_epoch(value, epoch)
}
pub fn try_push_back_with_epoch(&self, value: T, epoch: u64) -> Option<SlotId> {
let mut list = self.inner.try_write()?;
Some(list.push_back_with_epoch(value, epoch))
}
pub fn pop_front(&self) -> Option<T> {
let mut list = self.inner.write();
list.pop_front()
}
pub fn try_pop_front(&self) -> Option<Option<T>> {
let mut list = self.inner.try_write()?;
Some(list.pop_front())
}
pub fn pop_back(&self) -> Option<T> {
let mut list = self.inner.write();
list.pop_back()
}
pub fn try_pop_back(&self) -> Option<Option<T>> {
let mut list = self.inner.try_write()?;
Some(list.pop_back())
}
pub fn remove(&self, id: SlotId) -> Option<T> {
let mut list = self.inner.write();
list.remove(id)
}
pub fn try_remove(&self, id: SlotId) -> Option<Option<T>> {
let mut list = self.inner.try_write()?;
Some(list.remove(id))
}
pub fn move_to_front(&self, id: SlotId) -> bool {
let mut list = self.inner.write();
list.move_to_front(id)
}
pub fn try_move_to_front(&self, id: SlotId) -> Option<bool> {
let mut list = self.inner.try_write()?;
Some(list.move_to_front(id))
}
pub fn move_to_back(&self, id: SlotId) -> bool {
let mut list = self.inner.write();
list.move_to_back(id)
}
pub fn try_move_to_back(&self, id: SlotId) -> Option<bool> {
let mut list = self.inner.try_write()?;
Some(list.move_to_back(id))
}
pub fn get_with<R>(&self, id: SlotId, f: impl FnOnce(&T) -> R) -> Option<R> {
let list = self.inner.read();
list.get(id).map(f)
}
pub fn try_get_with<R>(&self, id: SlotId, f: impl FnOnce(&T) -> R) -> Option<Option<R>> {
let list = self.inner.try_read()?;
Some(list.get(id).map(f))
}
pub fn get_mut_with<R>(&self, id: SlotId, f: impl FnOnce(&mut T) -> R) -> Option<R> {
let mut list = self.inner.write();
list.get_mut(id).map(f)
}
pub fn try_get_mut_with<R>(
&self,
id: SlotId,
f: impl FnOnce(&mut T) -> R,
) -> Option<Option<R>> {
let mut list = self.inner.try_write()?;
Some(list.get_mut(id).map(f))
}
pub fn front_with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
let list = self.inner.read();
list.front().map(f)
}
pub fn try_front_with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<Option<R>> {
let list = self.inner.try_read()?;
Some(list.front().map(f))
}
pub fn back_with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<R> {
let list = self.inner.read();
list.back().map(f)
}
pub fn try_back_with<R>(&self, f: impl FnOnce(&T) -> R) -> Option<Option<R>> {
let list = self.inner.try_read()?;
Some(list.back().map(f))
}
pub fn epoch(&self, id: SlotId) -> Option<u64> {
let list = self.inner.read();
list.epoch(id)
}
pub fn try_epoch(&self, id: SlotId) -> Option<Option<u64>> {
let list = self.inner.try_read()?;
Some(list.epoch(id))
}
pub fn set_epoch(&self, id: SlotId, epoch: u64) -> bool {
let mut list = self.inner.write();
list.set_epoch(id, epoch)
}
pub fn try_set_epoch(&self, id: SlotId, epoch: u64) -> Option<bool> {
let mut list = self.inner.try_write()?;
Some(list.set_epoch(id, epoch))
}
pub fn clear(&self) {
let mut list = self.inner.write();
list.clear();
}
pub fn try_clear(&self) -> bool {
if let Some(mut list) = self.inner.try_write() {
list.clear();
true
} else {
false
}
}
pub fn clear_shrink(&self) {
let mut list = self.inner.write();
list.clear_shrink();
}
pub fn try_clear_shrink(&self) -> bool {
if let Some(mut list) = self.inner.try_write() {
list.clear_shrink();
true
} else {
false
}
}
pub fn approx_bytes(&self) -> usize {
let list = self.inner.read();
list.approx_bytes()
}
}
#[cfg(feature = "concurrency")]
impl<T> Default for ConcurrentIntrusiveList<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intrusive_list_basic_ops() {
let mut list = IntrusiveList::new();
let a = list.push_front("a");
let b = list.push_back("b");
let c = list.push_back("c");
assert_eq!(list.front(), Some(&"a"));
assert_eq!(list.back(), Some(&"c"));
assert_eq!(list.len(), 3);
assert!(list.move_to_front(c));
assert_eq!(list.front(), Some(&"c"));
assert_eq!(list.back(), Some(&"b"));
assert_eq!(list.remove(b), Some("b"));
assert_eq!(list.len(), 2);
assert_eq!(list.pop_front(), Some("c"));
assert_eq!(list.pop_back(), Some("a"));
assert!(list.is_empty());
assert!(!list.contains(a));
}
#[test]
#[cfg(feature = "concurrency")]
fn concurrent_intrusive_list_basic_ops() {
let list = ConcurrentIntrusiveList::new();
let a = list.push_front("a");
let b = list.push_back("b");
assert_eq!(list.front_with(|v| *v), Some("a"));
assert_eq!(list.back_with(|v| *v), Some("b"));
assert_eq!(list.len(), 2);
assert!(list.move_to_front(b));
assert_eq!(list.front_with(|v| *v), Some("b"));
assert_eq!(list.pop_back(), Some("a"));
assert_eq!(list.pop_back(), Some("b"));
assert!(list.is_empty());
assert!(!list.contains(a));
}
#[test]
fn intrusive_list_iter_order() {
let mut list = IntrusiveList::new();
list.push_back(1);
list.push_back(2);
list.push_back(3);
let values: Vec<_> = list.iter().copied().collect();
assert_eq!(values, vec![1, 2, 3]);
}
#[test]
fn intrusive_list_move_to_front_back_edges() {
let mut list = IntrusiveList::new();
let a = list.push_back("a");
let b = list.push_back("b");
let c = list.push_back("c");
assert!(list.move_to_front(a));
let values: Vec<_> = list.iter().copied().collect();
assert_eq!(values, vec!["a", "b", "c"]);
assert!(list.move_to_back(a));
let values: Vec<_> = list.iter().copied().collect();
assert_eq!(values, vec!["b", "c", "a"]);
assert!(list.move_to_front(c));
let values: Vec<_> = list.iter().copied().collect();
assert_eq!(values, vec!["c", "b", "a"]);
assert!(list.contains(b));
}
#[test]
fn intrusive_list_remove_middle_and_ends() {
let mut list = IntrusiveList::new();
let a = list.push_back("a");
let b = list.push_back("b");
let c = list.push_back("c");
assert_eq!(list.remove(b), Some("b"));
let values: Vec<_> = list.iter().copied().collect();
assert_eq!(values, vec!["a", "c"]);
assert_eq!(list.remove(a), Some("a"));
assert_eq!(list.front(), Some(&"c"));
assert_eq!(list.back(), Some(&"c"));
assert_eq!(list.remove(c), Some("c"));
assert!(list.is_empty());
assert_eq!(list.front(), None);
assert_eq!(list.back(), None);
}
#[test]
fn intrusive_list_clear_resets_state() {
let mut list = IntrusiveList::new();
list.push_back(1);
list.push_back(2);
list.clear();
assert!(list.is_empty());
assert_eq!(list.front(), None);
assert_eq!(list.back(), None);
assert_eq!(list.pop_front(), None);
assert_eq!(list.pop_back(), None);
}
#[test]
fn intrusive_list_get_mut_updates_value() {
let mut list = IntrusiveList::new();
let id = list.push_back(10);
if let Some(value) = list.get_mut(id) {
*value = 20;
}
assert_eq!(list.get(id), Some(&20));
}
#[test]
fn intrusive_list_id_and_entry_iters() {
let mut list = IntrusiveList::new();
let a = list.push_back("a");
let b = list.push_back("b");
let c = list.push_back("c");
assert_eq!(list.front_id(), Some(a));
assert_eq!(list.back_id(), Some(c));
let ids: Vec<_> = list.iter_ids().collect();
assert_eq!(ids, vec![a, b, c]);
let entries: Vec<_> = list.iter_entries().map(|(id, v)| (id, *v)).collect();
assert_eq!(entries, vec![(a, "a"), (b, "b"), (c, "c")]);
}
#[test]
fn intrusive_list_supports_standard_collection_traits() {
let mut list: IntrusiveList<_> = [1, 2, 3].into_iter().collect();
list.extend([4, 5]);
let from_iter: Vec<_> = list.iter().copied().collect();
assert_eq!(from_iter, vec![1, 2, 3, 4, 5]);
let via_ref_into_iter: Vec<_> = (&list).into_iter().copied().collect();
assert_eq!(via_ref_into_iter, vec![1, 2, 3, 4, 5]);
let owned: Vec<_> = list.into_iter().collect();
assert_eq!(owned, vec![1, 2, 3, 4, 5]);
}
#[test]
fn intrusive_list_into_iter_exact_size() {
let mut list = IntrusiveList::new();
list.push_back(10);
list.push_back(20);
list.push_back(30);
let mut iter = list.into_iter();
assert_eq!(iter.len(), 3);
assert_eq!(iter.next(), Some(10));
assert_eq!(iter.len(), 2);
assert_eq!(iter.next(), Some(20));
assert_eq!(iter.next(), Some(30));
assert_eq!(iter.next(), None);
assert_eq!(iter.len(), 0);
}
#[test]
fn intrusive_list_clone_preserves_order_and_epochs() {
let mut list = IntrusiveList::new();
list.push_back_with_epoch("a", 1);
list.push_back_with_epoch("b", 2);
list.push_back_with_epoch("c", 3);
let cloned = list.clone();
assert_eq!(cloned.len(), 3);
let orig_values: Vec<_> = list.iter().copied().collect();
let clone_values: Vec<_> = cloned.iter().copied().collect();
assert_eq!(orig_values, clone_values);
let orig_epochs: Vec<_> = list.iter_ids().filter_map(|id| list.epoch(id)).collect();
let clone_epochs: Vec<_> = cloned
.iter_ids()
.filter_map(|id| cloned.epoch(id))
.collect();
assert_eq!(orig_epochs, clone_epochs);
}
#[test]
fn intrusive_list_clone_is_independent() {
let mut list = IntrusiveList::new();
list.push_back(1);
list.push_back(2);
let mut cloned = list.clone();
cloned.push_back(3);
cloned.pop_front();
assert_eq!(list.len(), 2);
assert_eq!(cloned.len(), 2);
let orig: Vec<_> = list.iter().copied().collect();
let clone: Vec<_> = cloned.iter().copied().collect();
assert_eq!(orig, vec![1, 2]);
assert_eq!(clone, vec![2, 3]);
}
#[test]
fn intrusive_list_epoch_tracking() {
let mut list = IntrusiveList::new();
let a = list.push_front_with_epoch("a", 7);
let b = list.push_back("b");
assert_eq!(list.epoch(a), Some(7));
assert_eq!(list.epoch(b), Some(0));
assert!(list.set_epoch(b, 9));
assert_eq!(list.epoch(b), Some(9));
list.remove(b);
assert_eq!(list.epoch(b), None);
assert!(!list.set_epoch(b, 3));
}
#[test]
fn intrusive_list_debug_snapshot_ids() {
let mut list = IntrusiveList::new();
let a = list.push_back(1);
let b = list.push_back(2);
let c = list.push_back(3);
assert_eq!(list.debug_snapshot_ids(), vec![a, b, c]);
assert_eq!(list.debug_snapshot_ids_sorted(), vec![a, b, c]);
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_intrusive_list_clear_and_accessors() {
let list = ConcurrentIntrusiveList::new();
let a = list.push_front(1);
let b = list.push_back(2);
assert_eq!(list.get_with(a, |v| *v), Some(1));
assert_eq!(list.get_with(b, |v| *v), Some(2));
assert!(list.contains(a));
assert!(list.contains(b));
list.clear();
assert!(list.is_empty());
assert_eq!(list.front_with(|v| *v), None);
assert_eq!(list.back_with(|v| *v), None);
assert!(!list.contains(a));
assert!(!list.contains(b));
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_intrusive_list_try_ops() {
let list = ConcurrentIntrusiveList::new();
let a = list.try_push_front(1).unwrap();
let b = list.try_push_back(2).unwrap();
assert_eq!(list.try_get_with(a, |v| *v), Some(Some(1)));
assert_eq!(list.try_get_with(b, |v| *v), Some(Some(2)));
assert_eq!(list.try_front_with(|v| *v), Some(Some(1)));
assert!(list.try_move_to_back(a).unwrap());
assert_eq!(list.try_pop_front().unwrap(), Some(2));
assert_eq!(list.try_back_with(|v| *v), Some(Some(1)));
assert!(list.try_clear());
assert!(list.is_empty());
assert_eq!(list.try_front_with(|v| *v), Some(None));
assert_eq!(list.try_back_with(|v| *v), Some(None));
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_intrusive_list_try_accessors_preserve_missing_state() {
let list = ConcurrentIntrusiveList::new();
let id = list.push_back(1);
assert_eq!(list.try_get_with(id, |v| *v), Some(Some(1)));
assert_eq!(
list.try_get_mut_with(id, |v| {
*v = 2;
*v
}),
Some(Some(2))
);
assert_eq!(list.try_get_with(id, |v| *v), Some(Some(2)));
assert_eq!(list.remove(id), Some(2));
assert_eq!(list.try_get_with(id, |v| *v), Some(None));
assert_eq!(list.try_get_mut_with(id, |v| *v), Some(None));
}
#[cfg(feature = "concurrency")]
#[test]
fn concurrent_intrusive_list_epoch_ops() {
let list = ConcurrentIntrusiveList::new();
let a = list.push_front_with_epoch(1, 11);
let b = list.push_back(2);
assert_eq!(list.epoch(a), Some(11));
assert_eq!(list.epoch(b), Some(0));
assert!(list.set_epoch(b, 15));
assert_eq!(list.epoch(b), Some(15));
assert_eq!(list.try_epoch(a).unwrap(), Some(11));
assert!(list.try_set_epoch(a, 20).unwrap());
assert_eq!(list.epoch(a), Some(20));
}
#[test]
fn intrusive_list_debug_invariants_hold() {
let mut list = IntrusiveList::new();
let a = list.push_back(1);
let b = list.push_back(2);
let c = list.push_back(3);
list.move_to_front(b);
list.remove(a);
list.remove(c);
list.debug_validate_invariants();
}
#[test]
fn approx_bytes_no_double_count() {
let empty: IntrusiveList<u64> = IntrusiveList::new();
let struct_size = std::mem::size_of::<IntrusiveList<u64>>();
let reported = empty.approx_bytes();
assert_eq!(
reported,
struct_size,
"empty list: approx_bytes ({reported}) != size_of ({struct_size}), \
delta = {} — inline arena size is double-counted",
reported as isize - struct_size as isize
);
let mut list: IntrusiveList<u64> = IntrusiveList::new();
for i in 0..10 {
list.push_back(i);
}
let reported_full = list.approx_bytes();
assert!(
reported_full >= struct_size,
"non-empty list: approx_bytes ({reported_full}) < struct size ({struct_size})"
);
assert!(
reported_full > struct_size,
"non-empty list should have non-zero heap allocation"
);
}
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_fifo_ordering(
values in prop::collection::vec(any::<u32>(), 0..50)
) {
let mut list = IntrusiveList::new();
for value in &values {
list.push_back(*value);
}
for value in values {
let popped = list.pop_front();
prop_assert_eq!(popped, Some(value));
}
prop_assert!(list.is_empty());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_reverse_fifo_ordering(
values in prop::collection::vec(any::<u32>(), 0..50)
) {
let mut list = IntrusiveList::new();
for value in &values {
list.push_front(*value);
}
for value in values {
let popped = list.pop_back();
prop_assert_eq!(popped, Some(value));
}
prop_assert!(list.is_empty());
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_lifo_ordering(
values in prop::collection::vec(any::<u32>(), 0..50)
) {
let mut list = IntrusiveList::new();
for value in &values {
list.push_front(*value);
}
for value in values.iter().rev() {
let popped = list.pop_front();
prop_assert_eq!(popped, Some(*value));
}
prop_assert!(list.is_empty());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_reverse_lifo_ordering(
values in prop::collection::vec(any::<u32>(), 0..50)
) {
let mut list = IntrusiveList::new();
for value in &values {
list.push_back(*value);
}
for value in values.iter().rev() {
let popped = list.pop_back();
prop_assert_eq!(popped, Some(*value));
}
prop_assert!(list.is_empty());
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_move_to_front_is_mru(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut list = IntrusiveList::new();
let mut ids = Vec::new();
for value in &values {
let id = list.push_back(*value);
ids.push((id, *value));
}
for (id, value) in ids {
if list.contains(id) {
list.move_to_front(id);
prop_assert_eq!(list.front(), Some(&value));
prop_assert_eq!(list.front_id(), Some(id));
}
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_move_to_back_is_lru(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut list = IntrusiveList::new();
let mut ids = Vec::new();
for value in &values {
let id = list.push_front(*value);
ids.push((id, *value));
}
for (id, value) in ids {
if list.contains(id) {
list.move_to_back(id);
prop_assert_eq!(list.back(), Some(&value));
prop_assert_eq!(list.back_id(), Some(id));
}
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_move_operations_idempotent(
values in prop::collection::vec(any::<u32>(), 1..20),
repeat_count in 1usize..5
) {
let mut list = IntrusiveList::new();
for value in values {
let id = list.push_back(value);
for _ in 0..repeat_count {
prop_assert!(list.move_to_front(id));
prop_assert_eq!(list.front_id(), Some(id));
}
for _ in 0..repeat_count {
prop_assert!(list.move_to_back(id));
prop_assert_eq!(list.back_id(), Some(id));
}
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_decreases_length(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut list = IntrusiveList::new();
let mut ids = Vec::new();
for value in &values {
let id = list.push_back(*value);
ids.push((id, *value));
}
for (id, value) in ids {
let old_len = list.len();
let removed = list.remove(id);
prop_assert_eq!(removed, Some(value));
prop_assert_eq!(list.len(), old_len - 1);
prop_assert!(!list.contains(id));
prop_assert_eq!(list.get(id), None);
}
prop_assert!(list.is_empty());
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_remove_missing_returns_none(
values in prop::collection::vec(any::<u32>(), 1..20)
) {
let mut list = IntrusiveList::new();
for value in values {
let id = list.push_back(value);
let old_len = list.len();
let removed = list.remove(id);
prop_assert_eq!(removed, Some(value));
let removed_again = list.remove(id);
prop_assert_eq!(removed_again, None);
prop_assert_eq!(list.len(), old_len - 1);
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_len_tracks_elements(
values in prop::collection::vec(any::<u32>(), 0..50)
) {
let mut list = IntrusiveList::new();
let mut expected_len = 0;
for value in values {
list.push_back(value);
expected_len += 1;
prop_assert_eq!(list.len(), expected_len);
}
while !list.is_empty() {
list.pop_front();
expected_len -= 1;
prop_assert_eq!(list.len(), expected_len);
}
prop_assert_eq!(list.len(), 0);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_is_empty_consistent(
values in prop::collection::vec(any::<u32>(), 0..30)
) {
let mut list = IntrusiveList::new();
for value in values {
list.push_back(value);
if list.is_empty() {
prop_assert_eq!(list.len(), 0);
prop_assert_eq!(list.front(), None);
prop_assert_eq!(list.back(), None);
} else {
prop_assert!(!list.is_empty());
prop_assert!(list.front().is_some());
prop_assert!(list.back().is_some());
}
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_push_front_back_consistency(
front_values in prop::collection::vec(any::<u32>(), 1..20),
back_values in prop::collection::vec(any::<u32>(), 1..20)
) {
let mut list = IntrusiveList::new();
for value in &front_values {
list.push_front(*value);
prop_assert_eq!(list.front(), Some(value));
}
for value in &back_values {
list.push_back(*value);
prop_assert_eq!(list.back(), Some(value));
}
if let Some(&last_front) = front_values.last() {
prop_assert_eq!(list.front(), Some(&last_front));
}
if let Some(&last_back) = back_values.last() {
prop_assert_eq!(list.back(), Some(&last_back));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_front_back_id_consistency(
values in prop::collection::vec(any::<u32>(), 0..30)
) {
let mut list = IntrusiveList::new();
for value in values {
list.push_back(value);
if let Some(front_id) = list.front_id() {
prop_assert_eq!(list.get(front_id), list.front());
}
if let Some(back_id) = list.back_id() {
prop_assert_eq!(list.get(back_id), list.back());
}
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_contains_all_pushed(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut list = IntrusiveList::new();
let mut ids = Vec::new();
for value in values {
let id = list.push_back(value);
ids.push(id);
for &id in &ids {
prop_assert!(list.contains(id));
}
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_get_returns_correct_value(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut list = IntrusiveList::new();
let mut ids = Vec::new();
for value in &values {
let id = list.push_back(*value);
ids.push((id, *value));
}
for (id, value) in ids {
prop_assert_eq!(list.get(id), Some(&value));
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_resets_state(
values in prop::collection::vec(any::<u32>(), 1..30)
) {
let mut list = IntrusiveList::new();
for value in values {
list.push_back(value);
}
list.clear_shrink();
prop_assert!(list.is_empty());
prop_assert_eq!(list.len(), 0);
prop_assert_eq!(list.front(), None);
prop_assert_eq!(list.back(), None);
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_clear_invalidates_ids(
values in prop::collection::vec(any::<u32>(), 1..20)
) {
let mut list = IntrusiveList::new();
let mut ids = Vec::new();
for value in values {
let id = list.push_back(value);
ids.push(id);
}
list.clear_shrink();
for id in ids {
prop_assert!(!list.contains(id));
prop_assert_eq!(list.get(id), None);
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_usable_after_clear(
values1 in prop::collection::vec(any::<u32>(), 1..20),
values2 in prop::collection::vec(any::<u32>(), 1..20)
) {
let mut list = IntrusiveList::new();
for value in values1 {
list.push_back(value);
}
list.clear_shrink();
for value in &values2 {
list.push_back(*value);
}
prop_assert_eq!(list.len(), values2.len());
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_epoch_operations(
values in prop::collection::vec(any::<u32>(), 1..20),
epochs in prop::collection::vec(any::<u64>(), 1..20)
) {
let mut list = IntrusiveList::new();
let mut ids = Vec::new();
for value in &values {
let id = list.push_back(*value);
ids.push(id);
prop_assert_eq!(list.epoch(id), Some(0));
}
for (id, epoch) in ids.iter().zip(epochs.iter()) {
list.set_epoch(*id, *epoch);
prop_assert_eq!(list.epoch(*id), Some(*epoch));
}
}
#[cfg_attr(miri, ignore)]
#[test]
fn prop_push_with_epoch(
values in prop::collection::vec(any::<u32>(), 1..20),
epochs in prop::collection::vec(any::<u64>(), 1..20)
) {
let mut list = IntrusiveList::new();
for (value, epoch) in values.iter().zip(epochs.iter()) {
let id = list.push_back_with_epoch(*value, *epoch);
prop_assert_eq!(list.epoch(id), Some(*epoch));
}
}
}
proptest! {
#[cfg_attr(miri, ignore)]
#[test]
fn prop_matches_vecdeque_fifo(
operations in prop::collection::vec((0u8..4, any::<u32>()), 0..50)
) {
let mut list = IntrusiveList::new();
let mut reference = std::collections::VecDeque::new();
for (op, value) in operations {
match op % 4 {
0 => {
list.push_back(value);
reference.push_back(value);
}
1 => {
list.push_front(value);
reference.push_front(value);
}
2 => {
let list_val = list.pop_front();
let ref_val = reference.pop_front();
prop_assert_eq!(list_val, ref_val);
}
3 => {
let list_val = list.pop_back();
let ref_val = reference.pop_back();
prop_assert_eq!(list_val, ref_val);
}
_ => unreachable!(),
}
prop_assert_eq!(list.len(), reference.len());
prop_assert_eq!(list.front(), reference.front());
prop_assert_eq!(list.back(), reference.back());
prop_assert_eq!(list.is_empty(), reference.is_empty());
}
}
}
}