diff --git a/src/lib.rs b/src/lib.rs index 5194682..90db381 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,5 @@ /// A doubly linked list pub mod linked_list; + +/// A packed doubly linked list +pub mod packed_linked_list; diff --git a/src/packed_linked_list/mod.rs b/src/packed_linked_list/mod.rs new file mode 100644 index 0000000..405c189 --- /dev/null +++ b/src/packed_linked_list/mod.rs @@ -0,0 +1,158 @@ +#[cfg(test)] +mod test; + +use std::marker::PhantomData; +use std::mem::MaybeUninit; +use std::ptr::NonNull; + +fn allocate_nonnull(element: T) -> NonNull { + let boxed = Box::new(element); + // SAFETY: box is always non-null + unsafe { NonNull::new_unchecked(Box::leak(boxed)) } +} + +/// +/// A more efficient implementation of a linked list +/// +/// The packed linked list also consists of nodes, but each node contains several values, that may be copied +/// around for inserts, like in a `Vec`. These nodes are a lot smaller, so the copies should be fairly cheap, +/// trying to use the advantages of lists and mitigating the disadvantages of them (larger memory footprint, +/// no cache locality) by grouping several values together. +/// +/// Another way to optimize a linked list is by having a `Vec` of nodes that each have relative references, +/// but this implementation does not implement this. +pub struct PackedLinkedList { + first: Option>>, + last: Option>>, + _maker: PhantomData, +} + +impl PackedLinkedList { + pub fn new() -> Self { + Self { + first: None, + last: None, + _maker: PhantomData, + } + } + + pub fn push_front(&mut self, element: T) { + // SAFETY: All pointers should always point to valid memory, + unsafe { + match self.first { + None => { + self.insert_node_start(); + self.first.unwrap().as_mut().push_front(element) + } + Some(node) if node.as_ref().is_full() => { + self.insert_node_start(); + self.first.unwrap().as_mut().push_front(element) + } + Some(mut node) => node.as_mut().push_front(element), + } + } + } + + pub fn iter(&self) -> Iter { + Iter::new(self) + } + + fn insert_node_start(&mut self) { + let node = Some(allocate_nonnull(Node::new(None, self.first))); + self.first + .as_mut() + .map(|first| unsafe { first.as_mut().prev = node }); + self.first = node; + } +} + +#[derive(Debug)] +struct Node { + prev: Option>>, + next: Option>>, + values: [MaybeUninit; COUNT], + size: usize, +} + +impl Node { + fn new(prev: Option>>, next: Option>>) -> Self { + Self { + prev, + next, + // SAFETY: This is safe because we claim that the MaybeUninits are initialized, which they always are, + // since any uninitialized memory is a valid MaybeUninit + values: unsafe { MaybeUninit::uninit().assume_init() }, + size: 0, + } + } + + fn is_full(&self) -> bool { + self.size == COUNT + } + + /// Pushes a new value to the back + /// # Safety + /// The node must not be full + unsafe fn push_back(&mut self, element: T) { + debug_assert!(self.size < COUNT); + self.values[self.size] = MaybeUninit::new(element); + self.size += 1; + } + + /// Pushes a new value to the front + /// # Safety + /// The node must not be full + unsafe fn push_front(&mut self, element: T) { + debug_assert!(self.size < COUNT); + // copy all values up + unsafe { + std::ptr::copy( + &self.values[0] as *const _, + &mut self.values[1] as *mut _, + self.size, + ) + } + self.values[0] = MaybeUninit::new(element); + self.size += 1; + } +} + +pub struct Iter<'a, T, const COUNT: usize> { + node: Option<&'a Node>, + index: usize, +} + +impl<'a, T, const COUNT: usize> Iter<'a, T, COUNT> { + fn new(list: &'a PackedLinkedList) -> Self { + Self { + node: list.first.as_ref().map(|nn| unsafe { nn.as_ref() }), + index: 0, + } + } +} + +impl<'a, T, const COUNT: usize> Iterator for Iter<'a, T, COUNT> { + type Item = &'a T; + + fn next(&mut self) -> Option { + let node = self.node?; + // SAFETY: assume that all pointers point to the correct nodes, + // and that the sizes of the nodes are set correctly + unsafe { + if node.size > self.index { + // take more + let item = node.values[self.index].as_ptr().as_ref().unwrap(); + self.index += 1; + Some(item) + } else { + // next node + let next_node = node.next.as_ref()?.as_ref(); + self.index = 1; + self.node = Some(next_node); + // a node should never be empty + debug_assert_ne!(next_node.size, 0); + Some(next_node.values[0].as_ptr().as_ref().unwrap()) + } + } + } +} diff --git a/src/packed_linked_list/test.rs b/src/packed_linked_list/test.rs new file mode 100644 index 0000000..2a4982a --- /dev/null +++ b/src/packed_linked_list/test.rs @@ -0,0 +1,28 @@ +use super::*; + +#[test] +fn empty_unit_list() { + PackedLinkedList::<(), 0>::new(); +} + +#[test] +fn push_front_single_node() { + let mut list = PackedLinkedList::<_, 16>::new(); + list.push_front("hallo"); +} + +#[test] +fn iter_single_node() { + let mut list = PackedLinkedList::<_, 16>::new(); + list.push_front("2"); + list.push_front("1"); + let mut iter = list.iter(); + assert_eq!(iter.next(), Some(&"1")); + assert_eq!(iter.next(), Some(&"2")); + assert_eq!(iter.next(), None); +} + +fn create_list(iter: &[T]) -> PackedLinkedList { + //iter.into_iter().cloned().collect() + todo!() +}