#![expect(dead_code, reason = "library is still being implemented")]
use core::{
fmt::{self, Debug, Write},
iter,
ptr::NonNull,
};
#[cfg(any(debug_assertions, test))]
use std::collections::HashMap;
use arrayvec::ArrayVec;
use crate::node::link::Link;
pub(crate) mod link;
pub(crate) mod visitor;
#[derive(Debug)]
pub(crate) enum NodeType {
Head,
Tail,
Body,
Detached,
}
pub(crate) struct Node<V, const N: usize> {
next: Option<NonNull<Self>>,
prev: Option<NonNull<Self>>,
links: ArrayVec<Option<Link<V, N>>, N>,
value: Option<V>,
}
impl<V, const N: usize> Node<V, N> {
#[inline]
#[must_use]
pub(crate) fn new(max_levels: usize) -> Self {
debug_assert!(
max_levels <= N,
"max_levels ({max_levels}) exceeds node capacity ({N})"
);
Self {
next: None,
prev: None,
links: iter::repeat_with(|| None).take(max_levels).collect(),
value: None,
}
}
#[inline]
#[must_use]
pub(crate) fn with_value(height: usize, value: V) -> Self {
debug_assert!(height <= N, "height ({height}) exceeds node capacity ({N})");
Self {
next: None,
prev: None,
links: iter::repeat_with(|| None).take(height).collect(),
value: Some(value),
}
}
#[inline]
pub(crate) fn level(&self) -> usize {
self.links.len()
}
#[inline]
pub(crate) fn next_as_ref(&self) -> Option<&Self> {
self.next.map(|ptr| unsafe { ptr.as_ref() })
}
#[inline]
pub(crate) unsafe fn next_as_mut(&mut self) -> Option<&mut Self> {
self.next.map(|mut ptr| unsafe { ptr.as_mut() })
}
#[inline]
pub(crate) fn next(&self) -> Option<NonNull<Self>> {
self.next
}
#[inline]
pub(crate) fn prev_as_ref(&self) -> Option<&Self> {
self.prev.map(|ptr| unsafe { ptr.as_ref() })
}
#[inline]
pub(crate) unsafe fn prev_as_mut(&mut self) -> Option<&mut Self> {
self.prev.map(|mut ptr| unsafe { ptr.as_mut() })
}
#[inline]
pub(crate) fn prev(&self) -> Option<NonNull<Self>> {
self.prev
}
#[inline]
pub(crate) fn value(&self) -> Option<&V> {
self.value.as_ref()
}
#[inline]
pub(crate) fn value_mut(&mut self) -> Option<&mut V> {
self.value.as_mut()
}
#[inline]
pub(crate) fn take_value(&mut self) -> Option<V> {
self.value.take()
}
#[inline]
pub(crate) fn links(&self) -> &[Option<Link<V, N>>] {
&self.links
}
#[inline]
pub(crate) fn links_mut(&mut self) -> &mut [Option<Link<V, N>>] {
&mut self.links
}
#[inline]
fn node_type(&self) -> NodeType {
match (
self.prev.is_some(),
self.next.is_some(),
self.value.is_some(),
) {
(false, _, false) => NodeType::Head,
(false, false, true) => NodeType::Detached,
(true, true, true) => NodeType::Body,
(true, false, true) => NodeType::Tail,
_ => unreachable!("Invalid node state"),
}
}
#[expect(
clippy::unnecessary_box_returns,
reason = "pop() recovers the existing Box allocation created by insert_after(); \
returning Box<Self> signals heap ownership to callers"
)]
#[inline]
pub(crate) unsafe fn pop(&mut self) -> Box<Self> {
let _node_type = self.node_type();
debug_assert!(
!matches!(self.node_type(), NodeType::Head | NodeType::Detached),
"Cannot pop head or detached node"
);
let mut prev = self.prev.take();
let mut next = self.next.take();
if let Some(prev_ptr) = prev.as_mut() {
unsafe { prev_ptr.as_mut() }.next = next;
}
if let Some(next_ptr) = next.as_mut() {
unsafe { next_ptr.as_mut() }.prev = prev;
}
unsafe { Box::from_raw(self) }
}
#[inline]
pub(crate) unsafe fn truncate_next(&mut self) {
let mut current = self.next.take();
while let Some(ptr) = current {
let mut boxed: Box<Self> = unsafe { Box::from_raw(ptr.as_ptr()) };
current = boxed.next.take();
drop(boxed);
}
}
#[inline]
pub(crate) unsafe fn join(&mut self, mut head: Self) {
debug_assert!(
matches!(self.node_type(), NodeType::Tail),
"Can only join to tail node"
);
debug_assert!(
matches!(head.node_type(), NodeType::Head),
"Can only join with head node"
);
self.next = head.next.take();
if let Some(mut head_next) = self.next {
unsafe { head_next.as_mut() }.prev = Some(NonNull::from(&mut *self));
}
}
#[inline]
pub(crate) unsafe fn insert_after(
mut self_ptr: NonNull<Self>,
mut node: Self,
) -> NonNull<Self> {
debug_assert!(
matches!(node.node_type(), NodeType::Detached),
"Can only insert detached nodes."
);
let self_ref = unsafe { self_ptr.as_mut() };
node.prev = Some(self_ptr);
node.next = self_ref.next;
let node_ptr = unsafe { NonNull::new_unchecked(Box::into_raw(Box::new(node))) };
if let Some(next_ptr) = self_ref.next.as_mut() {
unsafe { next_ptr.as_mut() }.prev = Some(node_ptr);
}
self_ref.next = Some(node_ptr);
node_ptr
}
#[inline]
pub(crate) unsafe fn take_next_chain(&mut self) -> Option<NonNull<Self>> {
let first = self.next.take()?;
unsafe { &mut *first.as_ptr() }.prev = None;
Some(first)
}
#[inline]
pub(crate) unsafe fn set_head_next(&mut self, first: NonNull<Self>) {
debug_assert!(self.next.is_none(), "set_head_next: self.next must be None");
let self_nn = NonNull::from(&mut *self);
unsafe { &mut *first.as_ptr() }.prev = Some(self_nn);
self.next = Some(first);
}
#[expect(
clippy::expect_used,
reason = "Link::new(dist) returns Err only when dist == 0; \
dist = new_rank - pred_rank and new_rank > pred_rank \
whenever a predecessor is recorded, so dist >= 1 always"
)]
#[expect(
clippy::indexing_slicing,
reason = "l < node_height <= max_levels = predecessors.len() = self.level(); \
l indexes both predecessors[l] and links_mut()[l] so a plain index \
loop is the clearest expression"
)]
#[expect(
clippy::multiple_unsafe_ops_per_block,
reason = "raw-pointer traversal, link clearing, optional pop, and link wiring \
all touch provably disjoint heap nodes; grouping them avoids \
unsafe-crossing raw-pointer variables"
)]
pub(crate) unsafe fn filter_rebuild<F, D>(
head: NonNull<Self>,
mut keep: F,
mut on_drop: D,
) -> (usize, Option<NonNull<Self>>)
where
F: FnMut(*mut Self) -> bool,
D: FnMut(Box<Self>),
{
let head_ptr: *mut Self = head.as_ptr();
let max_levels = unsafe { (*head_ptr).level() };
let mut predecessors: ArrayVec<(*mut Self, usize), N> =
iter::repeat_n((head_ptr, 0_usize), max_levels).collect();
let mut new_rank: usize = 0;
let mut new_tail: Option<NonNull<Self>> = None;
unsafe {
for link in (*head_ptr).links_mut() {
*link = None;
}
let mut current_opt = (*head_ptr).next();
while let Some(cur_nn) = current_opt {
let cur: *mut Self = cur_nn.as_ptr();
let next_opt = (*cur).next();
if keep(cur) {
new_rank = new_rank.saturating_add(1);
new_tail = Some(cur_nn);
for link in (*cur).links_mut() {
*link = None;
}
let height = (*cur).level();
for l in 0..height {
let (pred_ptr, pred_rank) = predecessors[l];
let dist = new_rank.saturating_sub(pred_rank);
(*pred_ptr).links_mut()[l] = Some(
Link::new(NonNull::new_unchecked(cur), dist)
.expect("dist >= 1 by construction"),
);
predecessors[l] = (cur, new_rank);
}
} else {
on_drop((*cur).pop());
}
current_opt = next_opt;
}
}
(new_rank, new_tail)
}
#[inline]
pub(crate) unsafe fn rebuild(head: NonNull<Self>) -> Option<NonNull<Self>> {
let (_, tail) = unsafe { Self::filter_rebuild(head, |_| true, |_| {}) };
tail
}
}
#[cfg(any(debug_assertions, test))]
#[allow(
clippy::allow_attributes,
clippy::use_debug,
dead_code,
reason = "Used for debugging"
)]
impl<V: Debug, const N: usize> Node<V, N> {
#[inline]
fn ptr_index_map(&self) -> HashMap<NonNull<Self>, usize> {
let mut hm = HashMap::new();
let mut current = self;
let mut index = 0_usize;
loop {
hm.insert(NonNull::from(current), index);
if let Some(next) = current.next_as_ref() {
current = next;
index = index.saturating_add(1);
} else {
break;
}
}
hm
}
#[inline]
pub(crate) fn display(&self) -> Result<String, fmt::Error> {
let mut output = String::new();
write!(
output,
"{}\n\n{}",
self.display_links()?,
self.display_values()?
)?;
Ok(output)
}
#[inline]
fn display_links(&self) -> Result<String, fmt::Error> {
let hm = self.ptr_index_map();
let mut output = String::new();
for level in (0..self.level()).rev() {
write!(output, "[{:02}]: ", level.saturating_add(1))?;
let mut current = self;
loop {
if let Some(index) = hm.get(&NonNull::from(current)) {
write!(output, "{index:02}")?;
} else {
writeln!(output, "??")?;
break;
}
if let Some(Some(link)) = current.links().get(level) {
write!(
output,
" {}-> ",
"------".repeat(link.distance().get().saturating_sub(1))
)?;
current = unsafe { link.node().as_ref() };
} else {
writeln!(output)?;
break;
}
}
}
write!(output, "[->]: ")?;
let mut current = self;
loop {
if let Some(index) = hm.get(&NonNull::from(current)) {
write!(output, "{index:02}")?;
} else {
write!(output, "??")?;
}
if let Some(next) = current.next_as_ref() {
write!(output, " -> ")?;
current = next;
} else {
break;
}
}
let mut rev_string = String::new();
loop {
if let Some(index) = hm.get(&NonNull::from(current)) {
rev_string.insert_str(0, &format!("{index:02}"));
} else {
rev_string.insert_str(0, "??");
}
if let Some(prev) = current.prev_as_ref() {
rev_string.insert_str(0, " <- ");
current = prev;
} else {
break;
}
}
write!(output, "\n[<-]: {rev_string}")?;
Ok(output)
}
#[inline]
fn display_values(&self) -> Result<String, fmt::Error> {
let mut output = String::new();
let mut current = self;
let mut index = 0_usize;
loop {
writeln!(
output,
"[{:02}|{:02}] {:?}",
index,
current.level(),
current.value()
)?;
if let Some(node) = current.next_as_ref() {
current = node;
index = index.saturating_add(1);
} else {
break;
}
}
Ok(output)
}
#[inline]
fn display_ptrs(&self) -> Result<String, fmt::Error> {
let mut output = String::new();
let mut current = self;
let mut index = 0_usize;
loop {
writeln!(
output,
"[{:02}] {:?} <- {:?} -> {:?}",
index,
current.prev,
NonNull::from(current),
current.next
)?;
if let Some(node) = current.next_as_ref() {
current = node;
index = index.saturating_add(1);
} else {
break;
}
}
Ok(output)
}
}
impl<V: Debug, const N: usize> Debug for Node<V, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Node")
.field("next", &self.next)
.field("prev", &self.prev)
.field("links", &self.links)
.field("value", &self.value)
.finish()
}
}
impl<V, const N: usize> Drop for Node<V, N> {
fn drop(&mut self) {
while let Some(next_ptr) = self.next.take() {
let mut next_box: Box<Node<V, N>> = unsafe { Box::from_raw(next_ptr.as_ptr()) };
self.next = next_box.next.take();
drop(next_box);
}
}
}
#[expect(
clippy::undocumented_unsafe_blocks,
clippy::multiple_unsafe_ops_per_block,
clippy::indexing_slicing,
reason = "test code, covered by miri, so safety guarantees can be relaxed"
)]
#[cfg(test)]
pub(crate) mod tests {
use core::ptr::NonNull;
use anyhow::Result;
use insta::assert_snapshot;
use pretty_assertions::{assert_eq, assert_matches};
use rstest::{fixture, rstest};
use crate::node::{Node, NodeType, link::Link};
pub(crate) const MAX_LEVELS: usize = 3;
#[test]
fn node_new() {
let node = Node::<(), MAX_LEVELS>::new(MAX_LEVELS);
assert!(node.next.is_none());
assert!(node.prev.is_none());
assert_eq!(node.links.len(), MAX_LEVELS);
assert!(node.value.is_none());
}
#[test]
fn new_node_properties() {
let mut node = Node::<(), MAX_LEVELS>::new(MAX_LEVELS);
assert!(node.next_as_ref().is_none());
assert!(unsafe { node.next_as_mut() }.is_none());
assert!(node.prev_as_ref().is_none());
assert!(unsafe { node.prev_as_mut() }.is_none());
assert!(node.value().is_none());
assert!(node.value_mut().is_none());
}
#[fixture]
pub(crate) fn skiplist() -> Result<NonNull<Node<u8, MAX_LEVELS>>> {
let mut head_nn = unsafe {
NonNull::new_unchecked(Box::into_raw(Box::new(Node::<u8, MAX_LEVELS>::new(
MAX_LEVELS,
))))
};
let mut v1 = Node::<u8, MAX_LEVELS>::new(0);
let mut v2 = Node::<u8, MAX_LEVELS>::new(1);
let mut v3 = Node::<u8, MAX_LEVELS>::new(1);
let mut v4 = Node::<u8, MAX_LEVELS>::new(0);
v1.value = Some(10);
v2.value = Some(20);
v3.value = Some(30);
v4.value = Some(40);
unsafe {
let v1_ptr = Node::insert_after(head_nn, v1);
let mut v2_ptr = Node::insert_after(v1_ptr, v2);
let mut v3_ptr = Node::insert_after(v2_ptr, v3);
let v4_ptr = Node::insert_after(v3_ptr, v4);
head_nn.as_mut().links[1] = Some(Link::new(v3_ptr, 3)?);
head_nn.as_mut().links[0] = Some(Link::new(v2_ptr, 2)?);
v2_ptr.as_mut().links[0] = Some(Link::new(v3_ptr, 1)?);
v3_ptr.as_mut().links[0] = Some(Link::new(v4_ptr, 1)?);
}
Ok(head_nn)
}
#[rstest]
fn node_display(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00 -------------> 03
[01]: 00 -------> 02 -> 03 -> 04
[->]: 00 -> 01 -> 02 -> 03 -> 04
[<-]: 00 <- 01 <- 02 <- 03 <- 04
[00|03] None
[01|00] Some(10)
[02|01] Some(20)
[03|01] Some(30)
[04|00] Some(40)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn node_properties(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let head_ref = unsafe { head.as_ref() };
assert_matches!(head_ref.node_type(), NodeType::Head);
assert_eq!(head_ref.level(), 3);
let mut node = head_ref.next_as_ref().expect("v1 not found");
assert_matches!(node.node_type(), NodeType::Body);
assert_eq!(node.level(), 0);
node = node.next_as_ref().expect("v2 not found");
assert_matches!(node.node_type(), NodeType::Body);
assert_eq!(node.level(), 1);
node = node.next_as_ref().expect("v3 not found");
assert_matches!(node.node_type(), NodeType::Body);
assert_eq!(node.level(), 1);
node = node.next_as_ref().expect("v4 not found");
assert_matches!(node.node_type(), NodeType::Tail);
assert_eq!(node.level(), 0);
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn pop_node(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let v1 = unsafe { (*head.as_ptr()).next_as_mut() }.expect("v1 not found");
let v2 = unsafe { v1.next_as_mut() }.expect("v2 not found");
let detached_node = unsafe { v2.pop() };
assert_eq!(detached_node.value, Some(20));
assert!(matches!(detached_node.node_type(), NodeType::Detached));
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00 -------------> 02
[01]: 00 -------> ??
[->]: 00 -> 01 -> 02 -> 03
[<-]: 00 <- 01 <- 02 <- 03
[00|03] None
[01|00] Some(10)
[02|01] Some(30)
[03|00] Some(40)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn insert_after_head_node(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut new_node = Node::<u8, MAX_LEVELS>::new(MAX_LEVELS);
new_node.value = Some(100);
unsafe { Node::insert_after(head, new_node) };
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00 -------------> 04
[01]: 00 -------> 03 -> 04 -> 05
[->]: 00 -> 01 -> 02 -> 03 -> 04 -> 05
[<-]: 00 <- 01 <- 02 <- 03 <- 04 <- 05
[00|03] None
[01|03] Some(100)
[02|00] Some(10)
[03|01] Some(20)
[04|01] Some(30)
[05|00] Some(40)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn insert_after_body_node(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut new_node = Node::<u8, MAX_LEVELS>::new(MAX_LEVELS);
new_node.value = Some(100);
let v1 = unsafe { (*head.as_ptr()).next_as_mut() }.expect("v1 not found");
unsafe { Node::insert_after(NonNull::from_mut(v1), new_node) };
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00 -------------> 04
[01]: 00 -------> 03 -> 04 -> 05
[->]: 00 -> 01 -> 02 -> 03 -> 04 -> 05
[<-]: 00 <- 01 <- 02 <- 03 <- 04 <- 05
[00|03] None
[01|00] Some(10)
[02|03] Some(100)
[03|01] Some(20)
[04|01] Some(30)
[05|00] Some(40)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn insert_after_tail_node(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let mut new_node = Node::<u8, MAX_LEVELS>::new(MAX_LEVELS);
new_node.value = Some(100);
let v1 = unsafe { (*head.as_ptr()).next_as_mut() }.expect("v1 not found");
let v2 = unsafe { v1.next_as_mut() }.expect("v2 not found");
let v3 = unsafe { v2.next_as_mut() }.expect("v3 not found");
let v4 = unsafe { v3.next_as_mut() }.expect("v4 not found");
unsafe { Node::insert_after(NonNull::from_mut(v4), new_node) };
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00 -------------> 03
[01]: 00 -------> 02 -> 03 -> 04
[->]: 00 -> 01 -> 02 -> 03 -> 04 -> 05
[<-]: 00 <- 01 <- 02 <- 03 <- 04 <- 05
[00|03] None
[01|00] Some(10)
[02|01] Some(20)
[03|01] Some(30)
[04|00] Some(40)
[05|03] Some(100)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn filter_rebuild_empty_list() {
let mut head = Box::new(Node::<u8, MAX_LEVELS>::new(MAX_LEVELS));
let (new_len, new_tail) =
unsafe { Node::filter_rebuild(NonNull::from_mut(&mut *head), |_| true, |_| {}) };
assert_eq!(new_len, 0);
assert!(new_tail.is_none());
assert!(head.next_as_ref().is_none());
}
#[rstest]
fn filter_rebuild_keep_all(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let head = skiplist?;
let (new_len, new_tail) = unsafe { Node::filter_rebuild(head, |_| true, |_| {}) };
assert_eq!(new_len, 4);
let vals: Vec<u8> = {
let mut v = Vec::new();
let mut cur = unsafe { head.as_ref() }.next_as_ref();
while let Some(n) = cur {
v.push(*n.value().expect("data node"));
cur = n.next_as_ref();
}
v
};
assert_eq!(vals, [10, 20, 30, 40]);
let tail_val = unsafe { new_tail.expect("tail exists").as_ref() }
.value()
.copied();
assert_eq!(tail_val, Some(40));
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00
[01]: 00 -------> 02 -> 03
[->]: 00 -> 01 -> 02 -> 03 -> 04
[<-]: 00 <- 01 <- 02 <- 03 <- 04
[00|03] None
[01|00] Some(10)
[02|01] Some(20)
[03|01] Some(30)
[04|00] Some(40)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn filter_rebuild_keep_none(skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>) -> Result<()> {
let mut dropped_vals: Vec<u8> = Vec::new();
let head = skiplist?;
let (new_len, new_tail) = unsafe {
Node::filter_rebuild(
head,
|_| false,
|mut b| dropped_vals.push(b.take_value().expect("data node")),
)
};
assert_eq!(new_len, 0);
assert!(new_tail.is_none());
assert!(unsafe { head.as_ref() }.next_as_ref().is_none());
assert_eq!(dropped_vals, [10, 20, 30, 40]);
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00
[01]: 00
[->]: 00
[<-]: 00
[00|03] None
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn filter_rebuild_keep_first_and_third(
skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>,
) -> Result<()> {
let head = skiplist?;
let (new_len, new_tail) = unsafe {
Node::filter_rebuild(
head,
|cur| {
let v = (*cur).value().copied();
v == Some(10) || v == Some(30)
},
|_| {},
)
};
assert_eq!(new_len, 2);
let vals: Vec<u8> = {
let mut v = Vec::new();
let mut cur = unsafe { head.as_ref() }.next_as_ref();
while let Some(n) = cur {
v.push(*n.value().expect("data node"));
cur = n.next_as_ref();
}
v
};
assert_eq!(vals, [10, 30]);
let tail_val = unsafe { new_tail.expect("tail exists").as_ref() }
.value()
.copied();
assert_eq!(tail_val, Some(30));
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00
[01]: 00 -------> 02
[->]: 00 -> 01 -> 02
[<-]: 00 <- 01 <- 02
[00|03] None
[01|00] Some(10)
[02|01] Some(30)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn filter_rebuild_on_drop_receives_correct_values(
skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>,
) -> Result<()> {
let mut dropped: Vec<u8> = Vec::new();
let head = skiplist?;
unsafe {
Node::filter_rebuild(
head,
|cur| {
let v = (*cur).value().copied();
v == Some(20) || v == Some(40)
},
|mut b| dropped.push(b.take_value().expect("data node")),
);
}
assert_eq!(dropped, [10, 30]);
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn filter_rebuild_links_consistent_after_partial_keep(
skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>,
) -> Result<()> {
let head = skiplist?;
unsafe {
Node::filter_rebuild(
head,
|cur| {
let v = (*cur).value().copied();
v != Some(20) && v != Some(30)
},
|_| {},
);
}
let vals: Vec<u8> = {
let mut v = Vec::new();
let mut cur = unsafe { head.as_ref() }.next_as_ref();
while let Some(n) = cur {
v.push(*n.value().expect("data node"));
cur = n.next_as_ref();
}
v
};
assert_eq!(vals, [10, 40]);
for link in unsafe { head.as_ref() }.links() {
assert!(link.is_none(), "head skip link should be None");
}
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00
[01]: 00
[->]: 00 -> 01 -> 02
[<-]: 00 <- 01 <- 02
[00|03] None
[01|00] Some(10)
[02|00] Some(40)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn filter_rebuild_keep_all_links_rebuilt(
skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>,
) -> Result<()> {
let head = skiplist?;
unsafe {
Node::filter_rebuild(head, |_| true, |_| {});
}
let link0 = unsafe { head.as_ref() }.links()[0]
.as_ref()
.expect("head.links[0] must be Some");
assert_eq!(unsafe { link0.node().as_ref() }.value().copied(), Some(20));
assert_eq!(link0.distance().get(), 2);
assert!(
unsafe { head.as_ref() }.links()[1].is_none(),
"head.links[1] must be None"
);
assert!(
unsafe { head.as_ref() }.links()[2].is_none(),
"head.links[2] must be None"
);
let v2 = unsafe { head.as_ref() }
.next_as_ref()
.expect("v1")
.next_as_ref()
.expect("v2");
let v2_link0 = v2.links()[0].as_ref().expect("v2.links[0] must be Some");
assert_eq!(
unsafe { v2_link0.node().as_ref() }.value().copied(),
Some(30)
);
assert_eq!(v2_link0.distance().get(), 1);
let v3 = v2.next_as_ref().expect("v3");
assert!(v3.links()[0].is_none(), "v3.links[0] must be None");
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00
[01]: 00 -------> 02 -> 03
[->]: 00 -> 01 -> 02 -> 03 -> 04
[<-]: 00 <- 01 <- 02 <- 03 <- 04
[00|03] None
[01|00] Some(10)
[02|01] Some(20)
[03|01] Some(30)
[04|00] Some(40)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[test]
fn join_two_nonempty_lists() -> Result<()> {
let mut head1 = Box::new(Node::<u8, MAX_LEVELS>::new(MAX_LEVELS));
unsafe {
Node::insert_after(NonNull::from_mut(&mut *head1), Node::with_value(0, 10_u8));
Node::insert_after(
NonNull::from_mut(head1.next_as_mut().expect("v1")),
Node::with_value(0, 20_u8),
);
}
let mut head2 = Box::new(Node::<u8, MAX_LEVELS>::new(MAX_LEVELS));
unsafe {
Node::insert_after(NonNull::from_mut(&mut *head2), Node::with_value(0, 30_u8));
Node::insert_after(
NonNull::from_mut(head2.next_as_mut().expect("v3")),
Node::with_value(0, 40_u8),
);
}
unsafe {
let v2 = head1.next_as_mut().expect("v1").next_as_mut().expect("v2");
debug_assert!(matches!(v2.node_type(), NodeType::Tail));
v2.join(*head2);
}
let vals: Vec<u8> = {
let mut v = Vec::new();
let mut cur = head1.next_as_ref();
while let Some(n) = cur {
v.push(*n.value().expect("data node"));
cur = n.next_as_ref();
}
v
};
assert_eq!(vals, [10, 20, 30, 40]);
if !cfg!(miri) {
assert_snapshot!(
head1.display()?,
@"
[03]: 00
[02]: 00
[01]: 00
[->]: 00 -> 01 -> 02 -> 03 -> 04
[<-]: 00 <- 01 <- 02 <- 03 <- 04
[00|03] None
[01|00] Some(10)
[02|00] Some(20)
[03|00] Some(30)
[04|00] Some(40)
"
);
}
Ok(())
}
#[test]
fn join_empty_second_list() -> Result<()> {
let mut head1 = Box::new(Node::<u8, MAX_LEVELS>::new(MAX_LEVELS));
unsafe {
Node::insert_after(NonNull::from_mut(&mut *head1), Node::with_value(0, 10_u8));
Node::insert_after(
NonNull::from_mut(head1.next_as_mut().expect("v1")),
Node::with_value(0, 20_u8),
);
}
let head2 = Box::new(Node::<u8, MAX_LEVELS>::new(MAX_LEVELS));
unsafe {
let v2 = head1.next_as_mut().expect("v1").next_as_mut().expect("v2");
debug_assert!(matches!(v2.node_type(), NodeType::Tail));
v2.join(*head2);
}
let vals: Vec<u8> = {
let mut v = Vec::new();
let mut cur = head1.next_as_ref();
while let Some(n) = cur {
v.push(*n.value().expect("data node"));
cur = n.next_as_ref();
}
v
};
assert_eq!(vals, [10, 20]);
if !cfg!(miri) {
assert_snapshot!(
head1.display()?,
@"
[03]: 00
[02]: 00
[01]: 00
[->]: 00 -> 01 -> 02
[<-]: 00 <- 01 <- 02
[00|03] None
[01|00] Some(10)
[02|00] Some(20)
"
);
}
Ok(())
}
#[test]
fn truncate_next_on_middle_node() -> Result<()> {
let mut head = Box::new(Node::<u8, MAX_LEVELS>::new(MAX_LEVELS));
unsafe {
Node::insert_after(NonNull::from_mut(&mut *head), Node::with_value(0, 10_u8));
Node::insert_after(
NonNull::from_mut(head.next_as_mut().expect("v1")),
Node::with_value(0, 20_u8),
);
Node::insert_after(
NonNull::from_mut(head.next_as_mut().expect("v1").next_as_mut().expect("v2")),
Node::with_value(0, 30_u8),
);
}
unsafe { head.next_as_mut().expect("v1").truncate_next() };
let vals: Vec<u8> = {
let mut v = Vec::new();
let mut cur = head.next_as_ref();
while let Some(n) = cur {
v.push(*n.value().expect("data node"));
cur = n.next_as_ref();
}
v
};
assert_eq!(vals, [10]);
if !cfg!(miri) {
assert_snapshot!(
head.display()?,
@"
[03]: 00
[02]: 00
[01]: 00
[->]: 00 -> 01
[<-]: 00 <- 01
[00|03] None
[01|00] Some(10)
"
);
}
Ok(())
}
#[test]
fn truncate_next_empties_list() -> Result<()> {
let mut head = Box::new(Node::<u8, MAX_LEVELS>::new(MAX_LEVELS));
unsafe { Node::insert_after(NonNull::from_mut(&mut *head), Node::with_value(0, 10_u8)) };
unsafe {
head.truncate_next();
}
assert!(head.next_as_ref().is_none());
if !cfg!(miri) {
assert_snapshot!(
head.display()?,
@"
[03]: 00
[02]: 00
[01]: 00
[->]: 00
[<-]: 00
[00|03] None
"
);
}
Ok(())
}
#[test]
fn take_next_chain_returns_none_on_empty_head() {
let mut head = Box::new(Node::<u8, MAX_LEVELS>::new(MAX_LEVELS));
let result = unsafe { head.take_next_chain() };
assert!(result.is_none());
assert!(head.next_as_ref().is_none());
}
#[rstest]
fn take_next_chain_and_set_head_next_round_trip(
skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>,
) -> Result<()> {
let head = skiplist?;
let first_nn = unsafe { (*head.as_ptr()).take_next_chain() }.expect("fixture is non-empty");
assert!(
unsafe { head.as_ref() }.next_as_ref().is_none(),
"head must be empty after take"
);
assert!(
unsafe { first_nn.as_ref() }.prev_as_ref().is_none(),
"first detached node must have no prev"
);
let mut head2 = Box::new(Node::<u8, MAX_LEVELS>::new(MAX_LEVELS));
unsafe { head2.set_head_next(first_nn) };
let vals: Vec<u8> = {
let mut v = Vec::new();
let mut cur = head2.next_as_ref();
while let Some(n) = cur {
v.push(*n.value().expect("data node"));
cur = n.next_as_ref();
}
v
};
assert_eq!(vals, [10, 20, 30, 40]);
if !cfg!(miri) {
assert_snapshot!(
head2.display()?,
@"
[03]: 00
[02]: 00
[01]: 00
[->]: 00 -> 01 -> 02 -> 03 -> 04
[<-]: 00 <- 01 <- 02 <- 03 <- 04
[00|03] None
[01|00] Some(10)
[02|01] Some(20)
[03|01] Some(30)
[04|00] Some(40)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
#[rstest]
fn rebuild_produces_correct_links(
skiplist: Result<NonNull<Node<u8, MAX_LEVELS>>>,
) -> Result<()> {
let mut head = skiplist?;
for link in unsafe { head.as_mut() }.links_mut() {
*link = None;
}
let mut cur = unsafe { (*head.as_ptr()).next_as_mut() };
while let Some(n) = cur {
for link in n.links_mut() {
*link = None;
}
cur = unsafe { n.next_as_mut() };
}
let tail = unsafe { Node::rebuild(head) };
assert_eq!(
unsafe { tail.expect("non-empty").as_ref() }
.value()
.copied(),
Some(40)
);
if !cfg!(miri) {
assert_snapshot!(
unsafe { head.as_ref() }.display()?,
@"
[03]: 00
[02]: 00
[01]: 00 -------> 02 -> 03
[->]: 00 -> 01 -> 02 -> 03 -> 04
[<-]: 00 <- 01 <- 02 <- 03 <- 04
[00|03] None
[01|00] Some(10)
[02|01] Some(20)
[03|01] Some(30)
[04|00] Some(40)
"
);
}
unsafe { drop(Box::from_raw(head.as_ptr())) };
Ok(())
}
}