Skip to content

optimize linked_list - #66

Open
chirizxc wants to merge 6 commits into
awolverp:mainfrom
chirizxc:linked_list
Open

optimize linked_list#66
chirizxc wants to merge 6 commits into
awolverp:mainfrom
chirizxc:linked_list

Conversation

@chirizxc

@chirizxc chirizxc commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Compiler Explorer GodBolt

Compiler Explorer GodBolt Difff

How do I view the diff after clicking the link? 🤔 изображение изображение

TODO

Review // SAFETY and // # Safety again, most of them were generated by AI, and we need to double-check that they are correct

Benchmark

#![allow(dead_code)]

use divan::Bencher;

mod old {
    use std::marker::PhantomData;
    use std::mem;
    use std::ptr::NonNull;

    pub struct Node<T> {
        next: Option<NonNull<Node<T>>>,
        prev: Option<NonNull<Node<T>>>,
        element: T,
    }

    impl<T> Node<T> {
        fn new(element: T) -> Self {
            Node {
                next: None,
                prev: None,
                element,
            }
        }

        #[allow(clippy::boxed_local)]
        fn into_element(self: Box<Self>) -> T {
            self.element
        }

        pub fn element(&self) -> &T {
            &self.element
        }
    }

    pub struct LinkedList<T> {
        head: Option<NonNull<Node<T>>>,
        tail: Option<NonNull<Node<T>>>,
        len: usize,
        _marker: PhantomData<Box<Node<T>>>,
    }

    impl<T> LinkedList<T> {
        #[inline]
        unsafe fn push_front_node(&mut self, node: NonNull<Node<T>>) {
            unsafe {
                (*node.as_ptr()).next = self.head;
                (*node.as_ptr()).prev = None;
                let node = Some(node);

                match self.head {
                    None => self.tail = node,
                    Some(head) => (*head.as_ptr()).prev = node,
                }

                self.head = node;
                self.len += 1;
            }
        }

        #[inline]
        fn pop_front_node(&mut self) -> Option<Box<Node<T>>> {
            self.head.map(|node| unsafe {
                let node = Box::from_raw(node.as_ptr());
                self.head = node.next;

                match self.head {
                    None => self.tail = None,
                    Some(head) => (*head.as_ptr()).prev = None,
                }

                self.len -= 1;
                node
            })
        }

        #[inline]
        unsafe fn push_back_node(&mut self, node: NonNull<Node<T>>) {
            unsafe {
                (*node.as_ptr()).next = None;
                (*node.as_ptr()).prev = self.tail;
                let node = Some(node);

                match self.tail {
                    None => self.head = node,
                    Some(tail) => (*tail.as_ptr()).next = node,
                }

                self.tail = node;
                self.len += 1;
            }
        }

        #[inline]
        fn pop_back_node(&mut self) -> Option<Box<Node<T>>> {
            self.tail.map(|node| unsafe {
                let node = Box::from_raw(node.as_ptr());
                self.tail = node.prev;

                match self.tail {
                    None => self.head = None,
                    Some(tail) => (*tail.as_ptr()).next = None,
                }

                self.len -= 1;
                node
            })
        }

        #[inline]
        unsafe fn unlink_node(&mut self, mut node: NonNull<Node<T>>) {
            let node = unsafe { node.as_mut() };

            match node.prev {
                Some(prev) => unsafe { (*prev.as_ptr()).next = node.next },
                None => self.head = node.next,
            };

            match node.next {
                Some(next) => unsafe { (*next.as_ptr()).prev = node.prev },
                None => self.tail = node.prev,
            };

            self.len -= 1;
        }

        #[inline]
        unsafe fn remove_node(&mut self, node: NonNull<Node<T>>) -> T {
            unsafe {
                self.unlink_node(node);
                let node = Box::from_raw(node.as_ptr());
                node.element
            }
        }
    }

    impl<T> Default for LinkedList<T> {
        #[inline]
        fn default() -> Self {
            Self::new()
        }
    }

    impl<T> LinkedList<T> {
        #[inline]
        #[must_use]
        pub const fn new() -> Self {
            LinkedList {
                head: None,
                tail: None,
                len: 0,
                _marker: PhantomData,
            }
        }

        #[inline]
        #[must_use]
        pub fn is_empty(&self) -> bool {
            self.head.is_none()
        }

        #[inline]
        #[must_use]
        pub fn len(&self) -> usize {
            self.len
        }

        #[inline]
        pub fn clear(&mut self) {
            drop(LinkedList {
                head: self.head.take(),
                tail: self.tail.take(),
                len: mem::take(&mut self.len),
                _marker: PhantomData,
            });
        }

        #[inline]
        #[must_use]
        pub fn cursor_front(&self) -> Option<Cursor<T>> {
            self.head.map(Cursor::new)
        }

        #[inline]
        #[must_use]
        pub fn cursor_back(&self) -> Option<Cursor<T>> {
            self.tail.map(Cursor::new)
        }

        #[inline]
        pub fn push_front(&mut self, elt: T) -> Cursor<T> {
            let node = Box::new(Node::new(elt));
            let node_ptr = NonNull::from(Box::leak(node));

            unsafe {
                self.push_front_node(node_ptr);
            }
            Cursor::new(node_ptr)
        }

        #[inline]
        pub fn pop_front(&mut self) -> Option<T> {
            self.pop_front_node().map(Node::into_element)
        }

        #[inline]
        pub fn push_back(&mut self, elt: T) -> Cursor<T> {
            let node = Box::new(Node::new(elt));
            let node_ptr = NonNull::from(Box::leak(node));

            unsafe {
                self.push_back_node(node_ptr);
            }
            Cursor::new(node_ptr)
        }

        #[inline]
        pub fn pop_back(&mut self) -> Option<T> {
            self.pop_back_node().map(Node::into_element)
        }

        pub unsafe fn iter(&self) -> RawIter<T> {
            RawIter {
                head: self.head,
                len: self.len,
            }
        }
    }

    impl<T> Drop for LinkedList<T> {
        fn drop(&mut self) {
            struct DropGuard<'a, T>(&'a mut LinkedList<T>);

            impl<'a, T> Drop for DropGuard<'a, T> {
                fn drop(&mut self) {
                    while self.0.pop_front_node().is_some() {}
                }
            }

            let guard = DropGuard(self);
            while guard.0.pop_front_node().is_some() {}
            mem::forget(guard);
        }
    }

    #[repr(transparent)]
    pub struct Cursor<T>(NonNull<Node<T>>);

    impl<T> Clone for Cursor<T> {
        #[inline]
        fn clone(&self) -> Self {
            *self
        }
    }
    impl<T> Copy for Cursor<T> {}

    impl<T> PartialEq for Cursor<T> {
        #[inline]
        fn eq(&self, other: &Self) -> bool {
            self.0 == other.0
        }
    }
    impl<T> Eq for Cursor<T> {}

    impl<T> Cursor<T> {
        #[inline]
        fn new(node: NonNull<Node<T>>) -> Self {
            Cursor(node)
        }

        #[inline]
        pub unsafe fn element<'a>(&self) -> &'a T {
            &(*self.0.as_ptr()).element
        }

        #[inline]
        pub unsafe fn element_mut<'a>(&mut self) -> &'a mut T {
            &mut (*self.0.as_ptr()).element
        }

        #[inline]
        pub unsafe fn move_to_front(self, list: &mut LinkedList<T>) {
            list.unlink_node(self.0);
            list.push_front_node(self.0);
        }

        #[inline]
        pub unsafe fn move_to_back(self, list: &mut LinkedList<T>) {
            list.unlink_node(self.0);
            list.push_back_node(self.0);
        }

        #[inline]
        pub unsafe fn unlink(self, list: &mut LinkedList<T>) -> T {
            list.remove_node(self.0)
        }
    }

    pub struct RawIter<T> {
        head: Option<NonNull<Node<T>>>,
        len: usize,
    }

    impl<T> Iterator for RawIter<T> {
        type Item = Cursor<T>;

        #[inline]
        fn next(&mut self) -> Option<Cursor<T>> {
            if self.len == 0 {
                return None;
            }
            self.head.map(|node| {
                self.len -= 1;
                self.head = unsafe { (*node.as_ptr()).next };
                Cursor::new(node)
            })
        }

        #[inline]
        fn size_hint(&self) -> (usize, Option<usize>) {
            (self.len, Some(self.len))
        }
    }

    unsafe impl<T: Send + Send> Send for LinkedList<T> {}
    unsafe impl<T: Sync + Sync> Sync for LinkedList<T> {}
    unsafe impl<T: Send + Send> Send for RawIter<T> {}
    unsafe impl<T: Sync + Sync> Sync for RawIter<T> {}
    unsafe impl<T: Send + Send> Send for Cursor<T> {}
    unsafe impl<T: Sync + Sync> Sync for Cursor<T> {}
}

mod new {
    use std::alloc::alloc;
    use std::alloc::dealloc;
    use std::alloc::handle_alloc_error;
    use std::alloc::Layout;
    use std::marker::PhantomData;
    use std::mem;
    use std::ptr::NonNull;
    use std::ptr::{self};

    /// Intrusive doubly-linked pointers shared by every node and the sentinel.
    pub struct Links {
        prev: *mut Links,
        next: *mut Links,
    }

    impl Links {
        /// Returns a `Links` with both pointers null.
        #[inline]
        const fn empty() -> Self {
            Links {
                prev: ptr::null_mut(),
                next: ptr::null_mut(),
            }
        }
    }

    /// A single list element: link pointers plus the stored value.
    ///
    /// `#[repr(C)]` guarantees `links` is the first field at offset 0, so a
    /// `*mut Links` obtained from traversing the list can always be reinterpreted
    /// as `*mut Node<T>` (and vice versa) - this is what lets `Cursor<T>` and the
    /// sentinel-based traversal in `push_front_node`/`unlink_node` operate on
    /// plain `Links` pointers without knowing `T`.
    #[repr(C)]
    pub struct Node<T> {
        links: Links,
        element: T,
    }

    /// A doubly-linked list with an internal free-list for node reuse.
    pub struct LinkedList<T> {
        sentinel: NonNull<Links>,
        free_head: *mut Links,
        len: usize,
        _marker: PhantomData<Box<Node<T>>>,
    }

    impl<T> LinkedList<T> {
        /// Adds `node` to the front of the list.
        ///
        /// # Safety
        ///
        /// - `node` must point to a valid, currently unlinked `Links` belonging
        ///   to this list's allocations (freshly allocated via `alloc_node`/`get_node`,
        ///   or previously unlinked via `unlink_node`/`pop_*_node`).
        ///
        /// - The caller must not use `node` again as an "unlinked" node until it is
        ///   unlinked again.
        #[inline]
        unsafe fn push_front_node(&mut self, node: NonNull<Links>) {
            let s = self.sentinel.as_ptr();
            let n = node.as_ptr();
            unsafe {
                // SAFETY: `s` is the sentinel, always valid; `n` is valid per caller contract.
                let first = (*s).next;
                (*n).prev = s;
                (*n).next = first;
                (*first).prev = n;
                (*s).next = n;
            }
        }

        /// Adds `node` to the back of the list.
        ///
        /// # Safety
        ///
        /// Same contract as [`push_front_node`](Self::push_front_node).
        #[inline]
        unsafe fn push_back_node(&mut self, node: NonNull<Links>) {
            let s = self.sentinel.as_ptr();
            let n = node.as_ptr();
            unsafe {
                // SAFETY: `s` is the sentinel, always valid; `n` is valid per caller contract.
                let last = (*s).prev;
                (*n).next = s;
                (*n).prev = last;
                (*last).next = n;
                (*s).prev = n;
            }
        }

        /// Removes and returns the node at the front of the list, if any.
        #[inline]
        fn pop_front_node(&mut self) -> Option<NonNull<Links>> {
            if self.len == 0 {
                return None;
            }
            let s = self.sentinel.as_ptr();
            let node = unsafe {
                // SAFETY: `self.len != 0`, so `(*s).next` points to a real node,
                // and its `next` (`second`) is either another real node or `s` itself.
                let node = (*s).next;
                let second = (*node).next;
                (*second).prev = s;
                (*s).next = second;
                node
            };
            self.len -= 1;
            // SAFETY: `node` was just read from a valid linked node, hence non-null.
            Some(unsafe { NonNull::new_unchecked(node) })
        }

        /// Removes and returns the node at the back of the list, if any.
        #[inline]
        fn pop_back_node(&mut self) -> Option<NonNull<Links>> {
            if self.len == 0 {
                return None;
            }
            let s = self.sentinel.as_ptr();
            let node = unsafe {
                // SAFETY: `self.len != 0`, so `(*s).prev` points to a real node.
                let node = (*s).prev;
                let before = (*node).prev;
                (*before).next = s;
                (*s).prev = before;
                node
            };
            self.len -= 1;
            // SAFETY: `node` was just read from a valid linked node, hence non-null.
            Some(unsafe { NonNull::new_unchecked(node) })
        }

        /// Unlinks `node` from the list, without deallocating or reading its element.
        ///
        /// # Safety
        ///
        /// `node` must point to a node currently linked into this list (not the sentinel).
        #[inline]
        unsafe fn unlink_node(&mut self, node: NonNull<Links>) {
            let n = node.as_ptr();
            unsafe {
                // SAFETY: caller guarantees `n` is linked, so `prev`/`next` point to
                // valid nodes (or the sentinel).
                let prev = (*n).prev;
                let next = (*n).next;
                (*prev).next = next;
                (*next).prev = prev;
            }
        }

        /// Pushes `node` onto the internal free list for reuse.
        ///
        /// # Safety
        ///
        /// `node` must be unlinked and its `element` must already be logically
        /// moved out (dropped or read via `ptr::read`) — this only reuses the
        /// `Links`/allocation, not the `T` storage.
        #[inline]
        unsafe fn recycle_node(&mut self, node: *mut Links) {
            let free_head = self.free_head;
            unsafe {
                // SAFETY: `node` is valid per caller contract; writing `next` does
                // not touch `element`.
                (*node).next = free_head;
            }
            self.free_head = node;
        }

        /// Reads the element out of `node` and recycles the node's storage.
        ///
        /// # Safety
        ///
        /// `node` must point to a node whose `element` is initialized and which is
        /// no longer linked into the list (already unlinked by the caller).
        #[inline]
        unsafe fn take_element_and_recycle(&mut self, node: NonNull<Links>) -> T {
            let node_ptr = node.as_ptr() as *mut Node<T>;
            // SAFETY: caller guarantees `element` is initialized; reading it does
            // not run its destructor, so no double-drop.
            let element = unsafe { ptr::read(&(*node_ptr).element) };
            // SAFETY: the element has been logically moved out; the node is safe to recycle.
            unsafe { self.recycle_node(node.as_ptr()) };
            element
        }

        /// Unlinks `node` from the list and returns its element.
        ///
        /// # Safety
        ///
        /// `node` must point to a node currently linked into this list
        #[inline]
        unsafe fn remove_node(&mut self, node: NonNull<Links>) -> T {
            // SAFETY: caller guarantees `node` is linked.
            unsafe { self.unlink_node(node) };
            self.len -= 1;
            // SAFETY: `node` was just unlinked, and its `element` is still initialized.
            unsafe { self.take_element_and_recycle(node) }
        }

        /// Drops every linked element and parks its node on the free list.
        ///
        /// Elements whose destructors panic are handled by the caller's
        /// drop-guard strategy; nodes are recycled even on the panic path.
        fn drop_nodes(&mut self) {
            let s = self.sentinel.as_ptr();
            while self.len != 0 {
                unsafe {
                    // SAFETY: `self.len != 0`, so `(*s).next` points to a real node.
                    let node = (*s).next;
                    let second = (*node).next;
                    (*second).prev = s;
                    (*s).next = second;

                    let node_ptr = node as *mut Node<T>;
                    // SAFETY: `#[repr(C)]` puts `links` at offset 0, so `node` is a
                    // valid `Node<T>` whose `element` is still initialized.
                    ptr::drop_in_place(&mut (*node_ptr).element);
                    // SAFETY: the element was just dropped; the node is unlinked.
                    self.recycle_node(node);
                }
                self.len -= 1;
            }
        }

        /// Frees every entry on the internal free list.
        ///
        /// # Safety
        ///
        /// Must only be called when no other references into the free list exist
        /// (e.g. from `Drop`); each recycled node must have been allocated with
        /// `Layout::new::<Node<T>>()`.
        unsafe fn drop_freelist(&mut self) {
            let mut node = self.free_head;
            while !node.is_null() {
                // SAFETY: `node` is a non-null pointer previously pushed by
                // `recycle_node`, so it points to a valid `Node<T>` allocation.
                let next = unsafe { (*node).next };
                // SAFETY: `node` was allocated via the global allocator with this
                // exact layout (see `alloc_node`/`LinkedList::new`), and is not
                // used again afterward.
                unsafe { dealloc(node as *mut u8, Layout::new::<Node<T>>()) };
                node = next;
            }
            self.free_head = ptr::null_mut();
        }

        /// Returns a node holding `elt`: reuses a free-list node when one is
        /// available, otherwise allocates a fresh one.
        #[inline]
        fn get_node(&mut self, elt: T) -> *mut Node<T> {
            if !self.free_head.is_null() {
                let links = self.free_head;
                // SAFETY: `free_head` is non-null here and points to a recycled
                // node threaded via `recycle_node`.
                self.free_head = unsafe { (*links).next };
                let node = links as *mut Node<T>;
                // SAFETY: `#[repr(C)]` puts `links` at offset 0, and only the
                // `element` slot of a recycled node is stale; `links` is
                // rewritten by the push that follows.
                unsafe { ptr::write(&mut (*node).element, elt) };
                node
            } else {
                Self::alloc_node(elt)
            }
        }

        /// Cold fallback of `get_node`: allocates a brand-new node.
        #[inline(never)]
        #[cold]
        fn alloc_node(elt: T) -> *mut Node<T> {
            Box::into_raw(Box::new(Node {
                links: Links::empty(),
                element: elt,
            }))
        }
    }

    impl<T> Default for LinkedList<T> {
        /// Creates an empty `LinkedList<T>`.
        #[inline]
        fn default() -> Self {
            Self::new()
        }
    }

    impl<T> LinkedList<T> {
        /// Creates an empty `LinkedList`.
        #[inline]
        #[must_use]
        pub fn new() -> Self {
            let layout = Layout::new::<Links>();
            let raw = unsafe {
                let p = alloc(layout) as *mut Links;
                if p.is_null() {
                    handle_alloc_error(layout);
                }
                // SAFETY: `p` was just allocated with `layout` and checked non-null
                // above; writing a fresh self-referential `Links` into it is a
                // valid initialization of that memory.
                ptr::write(p, Links { prev: p, next: p });
                p
            };
            // SAFETY: `p` was checked non-null (or diverged via `handle_alloc_error`).
            let sentinel = unsafe { NonNull::new_unchecked(raw) };
            LinkedList {
                sentinel,
                free_head: ptr::null_mut(),
                len: 0,
                _marker: PhantomData,
            }
        }

        /// Returns `true` if the list contains no elements.
        #[inline]
        #[must_use]
        pub fn is_empty(&self) -> bool {
            self.len == 0
        }

        /// Returns the number of elements in the list.
        #[inline]
        #[must_use]
        pub fn len(&self) -> usize {
            self.len
        }

        /// Removes all elements, dropping each element's value.
        #[inline]
        pub fn clear(&mut self) {
            self.drop_nodes();
        }

        /// Returns a cursor to the front element, or `None` if the list is empty.
        #[inline]
        #[must_use]
        pub fn cursor_front(&self) -> Option<Cursor<T>> {
            if self.len == 0 {
                return None;
            }
            let s = self.sentinel.as_ptr();
            // SAFETY: sentinel is always valid; `self.len != 0` guarantees
            // `(*s).next` points to a real, linked node rather than back to `s`.
            let node = unsafe { (*s).next };
            // SAFETY: `node` is non-null (established above) and, by `#[repr(C)]`
            // on `Node<T>`, `*mut Links` and `*mut Node<T>` share the same address.
            Some(Cursor(unsafe {
                NonNull::new_unchecked(node as *mut Node<T>)
            }))
        }

        /// Returns a cursor to the back element, or `None` if the list is empty.
        #[inline]
        #[must_use]
        pub fn cursor_back(&self) -> Option<Cursor<T>> {
            if self.len == 0 {
                return None;
            }
            let s = self.sentinel.as_ptr();
            // SAFETY: sentinel is always valid; `self.len != 0` guarantees
            // `(*s).prev` points to a real, linked node.
            let node = unsafe { (*s).prev };
            // SAFETY: same reasoning as in `cursor_front`.
            Some(Cursor(unsafe {
                NonNull::new_unchecked(node as *mut Node<T>)
            }))
        }

        /// Inserts `elt` at the front of the list and returns a cursor to it.
        #[inline]
        pub fn push_front(&mut self, elt: T) -> Cursor<T> {
            let node = self.get_node(elt);
            // SAFETY: `get_node` always returns a non-null, freshly-usable `Node<T>` pointer.
            let links = unsafe { NonNull::new_unchecked(node as *mut Links) };

            // SAFETY: `links` refers to a node that was just obtained (allocated
            // or recycled) and is not linked into any list yet.
            unsafe { self.push_front_node(links) };
            self.len += 1;
            // SAFETY: `node` is the same non-null pointer validated above.
            Cursor(unsafe { NonNull::new_unchecked(node) })
        }

        /// Removes and returns the front element, or `None` if the list is empty.
        #[inline]
        pub fn pop_front(&mut self) -> Option<T> {
            let node = self.pop_front_node()?;
            // SAFETY: `node` was just unlinked by `pop_front_node`, so its
            // `element` is still initialized and it is safe to take/recycle.
            let element = unsafe { self.take_element_and_recycle(node) };
            Some(element)
        }

        /// Inserts `elt` at the back of the list and returns a cursor to it.
        #[inline]
        pub fn push_back(&mut self, elt: T) -> Cursor<T> {
            let node = self.get_node(elt);
            // SAFETY: `get_node` always returns a non-null, freshly-usable `Node<T>` pointer.
            let links = unsafe { NonNull::new_unchecked(node as *mut Links) };

            // SAFETY: `links` refers to a node that was just obtained and is
            // not linked into any list yet.
            unsafe { self.push_back_node(links) };
            self.len += 1;
            // SAFETY: `node` is the same non-null pointer validated above.
            Cursor(unsafe { NonNull::new_unchecked(node) })
        }

        /// Removes and returns the back element, or `None` if the list is empty.
        #[inline]
        pub fn pop_back(&mut self) -> Option<T> {
            let node = self.pop_back_node()?;
            // SAFETY: `node` was just unlinked by `pop_back_node`, so its
            // `element` is still initialized and it is safe to take/recycle.
            let element = unsafe { self.take_element_and_recycle(node) };
            Some(element)
        }

        /// Returns a raw, unsynchronized iterator over cursors into the list.
        ///
        /// # Safety
        ///
        /// The caller must not mutate or drop the list while the returned
        /// `RawIter` (or any `Cursor` obtained from it) is in use, and must not
        /// call [`Cursor::unlink`], [`Cursor::move_to_front`], or
        /// [`Cursor::move_to_back`] on a yielded cursor while iteration is still
        /// in progress, since that would invalidate `RawIter::next`.
        #[inline]
        pub unsafe fn iter(&self) -> RawIter<T> {
            let s = self.sentinel.as_ptr();
            // SAFETY: sentinel is always valid, regardless of `self.len`.
            let next = unsafe { (*s).next };
            RawIter {
                next,
                len: self.len,
                _marker: PhantomData,
            }
        }
    }

    // Guard ensures that if dropping an element panics, we still free
    // the free-list allocations instead of leaking them (the sentinel
    // is freed unconditionally below regardless of panics).
    struct DropGuard<'a, T>(&'a mut LinkedList<T>);

    impl<'a, T> Drop for DropGuard<'a, T> {
        fn drop(&mut self) {
            self.0.drop_nodes();
            // SAFETY: called only during unwind cleanup, after `drop_nodes`
            // has already run (or partially run); the free list is not
            // accessed again afterward.
            unsafe { self.0.drop_freelist() };
        }
    }

    impl<T> Drop for LinkedList<T> {
        fn drop(&mut self) {
            let guard = DropGuard(self);
            guard.0.drop_nodes();
            // SAFETY: `drop_nodes` completed without panicking; the free list is
            // only touched here and then never again (guard is forgotten next).
            unsafe { guard.0.drop_freelist() };
            mem::forget(guard);
            // SAFETY: `self.sentinel` was allocated in `new` with exactly this
            // layout (`Layout::new::<Links>()`), and after this point `self` is
            // being destroyed, so the pointer is never dereferenced again.
            unsafe {
                dealloc(self.sentinel.as_ptr() as *mut u8, Layout::new::<Links>());
            }
        }
    }

    /// A handle to a single node in a `LinkedList`.
    ///
    /// `Cursor` is a thin, `Copy`able pointer to a node, obtained from
    /// [`LinkedList::push_front`], [`LinkedList::push_back`],
    /// [`LinkedList::cursor_front`], or [`LinkedList::cursor_back`], and used to
    /// later access or reposition that node via [`Cursor::element`],
    /// [`Cursor::move_to_front`], [`Cursor::move_to_back`], or [`Cursor::unlink`].
    ///
    /// `#[repr(transparent)]` over `NonNull<Node<T>>` means a `Cursor<T>` has the
    /// exact same layout as the raw pointer it wraps — no extra state is tracked,
    /// so the cursor does *not* know which `LinkedList` it came from, whether the
    /// node is still linked, or whether other `Cursor`s alias the same node. All
    /// of that is the caller's responsibility, which is why every non-trivial
    /// method on `Cursor` is `unsafe`.
    ///
    /// Because it is just a pointer, `Cursor<T>` is cheaply `Copy`/`Clone`, and
    /// equality (`PartialEq`/`Eq`) compares the underlying pointer, i.e. identity
    /// of the node, not the value of the element.
    #[repr(transparent)]
    pub struct Cursor<T>(NonNull<Node<T>>);

    // `NonNull<Node<T>>` is just a pointer; copying it is always safe.
    impl<T> Clone for Cursor<T> {
        #[inline]
        fn clone(&self) -> Self {
            *self
        }
    }
    impl<T> Copy for Cursor<T> {}

    // Pointer equality: two cursors are equal if they point at the same node.
    impl<T> PartialEq for Cursor<T> {
        #[inline]
        fn eq(&self, other: &Self) -> bool {
            self.0 == other.0
        }
    }
    impl<T> Eq for Cursor<T> {}

    impl<T> Cursor<T> {
        /// Returns the underlying node as a `Links` pointer, for use with the
        /// list's internal `Links`-based operations.
        ///
        /// Relies on `#[repr(C)]` on `Node<T>` placing `links` at offset 0, so the
        /// cast is always valid regardless of `T`.
        #[inline]
        fn links(&self) -> NonNull<Links> {
            let ptr = self.0.as_ptr() as *mut Links;
            // SAFETY: `self.0` is `NonNull`, so the reinterpreted pointer is non-null too.
            unsafe { NonNull::new_unchecked(ptr) }
        }

        /// Returns a reference to the node's element, with a caller-chosen lifetime.
        ///
        /// # Safety
        ///
        /// - The node this cursor points to must still be linked (or otherwise
        ///   kept alive) in some `LinkedList<T>`, i.e. not yet unlinked/recycled.
        ///
        /// - The returned `&'a T` must not outlive the underlying allocation, and
        ///   no `&mut T`/`element_mut` alias to the same node may exist while
        ///   this reference is live.
        #[inline]
        pub unsafe fn element<'a>(&self) -> &'a T {
            let node = self.0.as_ptr();
            // SAFETY: caller guarantees the node is still allocated/linked and
            // that no conflicting `&mut` aliases the element.
            unsafe { &(*node).element }
        }

        /// Returns a mutable reference to the node's element, with a caller-chosen lifetime.
        ///
        /// # Safety
        ///
        /// Same contract as [`element`](Self::element), plus: no other reference
        /// (shared or mutable) to this node's element may be alive at the same time.
        #[inline]
        pub unsafe fn element_mut<'a>(&mut self) -> &'a mut T {
            let node = self.0.as_ptr();
            // SAFETY: caller guarantees exclusive access to this node's element.
            unsafe { &mut (*node).element }
        }

        /// Moves the node this cursor points to the front of `list`.
        ///
        /// # Safety
        ///
        /// - The node must currently be linked into `list` (not some other list,
        ///   and not already unlinked/recycled).
        ///
        /// - No other `Cursor`/reference into this node may be used concurrently
        ///   with this call.
        #[inline]
        pub unsafe fn move_to_front(self, list: &mut LinkedList<T>) {
            let links = self.links();
            // SAFETY: caller guarantees `links` is currently linked into `list`.
            unsafe { list.unlink_node(links) };
            // SAFETY: `links` was just unlinked above, so it's safe to relink.
            unsafe { list.push_front_node(links) };
        }

        /// Moves the node this cursor points to the back of `list`.
        ///
        /// # Safety
        ///
        /// Same contract as [`move_to_front`](Self::move_to_front).
        #[inline]
        pub unsafe fn move_to_back(self, list: &mut LinkedList<T>) {
            let links = self.links();
            // SAFETY: caller guarantees `links` is currently linked into `list`.
            unsafe { list.unlink_node(links) };
            // SAFETY: `links` was just unlinked above, so it's safe to relink.
            unsafe { list.push_back_node(links) };
        }

        /// Removes the node this cursor points to from `list` and returns its element.
        ///
        /// # Safety
        ///
        /// - The node must currently be linked into `list`.
        ///
        /// - This consumes the cursor (`self`, by value) because the node is
        ///   deallocated/recycled afterward — the cursor must not be used again.
        #[inline]
        pub unsafe fn unlink(self, list: &mut LinkedList<T>) -> T {
            let links = self.links();
            // SAFETY: caller guarantees `links` is currently linked into `list`.
            unsafe { list.remove_node(links) }
        }
    }

    /// A raw, unsynchronized iterator over `Cursor`s in a `LinkedList`.
    ///
    /// Created only via [`LinkedList::iter`], which is itself `unsafe` — see its
    /// `# Safety` section for the invariants that make walking `next`/`len` here
    /// sound (the list must not be mutated or dropped while this iterator, or any
    /// `Cursor` it yields, is in use).
    pub struct RawIter<T> {
        next: *mut Links,
        len: usize,
        _marker: PhantomData<NonNull<Node<T>>>,
    }

    impl<T> Iterator for RawIter<T> {
        type Item = Cursor<T>;

        #[inline]
        fn next(&mut self) -> Option<Cursor<T>> {
            if self.len == 0 {
                return None;
            }
            let node = self.next;
            self.len -= 1;
            // SAFETY: `self.len != 0` (checked above) guarantees `node` is a
            // currently-valid, linked node, so `(*node).next` is a valid read;
            // per `LinkedList::iter`'s contract, the list is not mutated/dropped
            // while this iterator is alive.
            self.next = unsafe { (*node).next };
            // SAFETY: `node` is non-null (came from a valid linked `Links`), and
            // by `#[repr(C)]` on `Node<T>` a `*mut Links` is a valid `*mut Node<T>`.
            let cursor = Cursor(unsafe { NonNull::new_unchecked(node as *mut Node<T>) });
            Some(cursor)
        }

        #[inline]
        fn size_hint(&self) -> (usize, Option<usize>) {
            (self.len, Some(self.len))
        }
    }

    unsafe impl<T: Send + Send> Send for LinkedList<T> {}
    unsafe impl<T: Sync + Sync> Sync for LinkedList<T> {}
    unsafe impl<T: Send + Send> Send for RawIter<T> {}
    unsafe impl<T: Sync + Sync> Sync for RawIter<T> {}
    unsafe impl<T: Send + Send> Send for Cursor<T> {}
    unsafe impl<T: Sync + Sync> Sync for Cursor<T> {}
}

const FILL: usize = 4096;
const CAP: usize = 1024;

#[derive(Clone, Copy)]
pub struct Elem {
    pub a: u64,
    pub b: u64,
    pub c: u64,
    pub d: u64,
    pub e: u64,
}

impl Elem {
    #[inline]
    pub fn new(seed: u64) -> Self {
        Elem {
            a: seed,
            b: seed.wrapping_mul(0x9E37_79B9_7F4A_7C15),
            c: seed.wrapping_add(0x1234_5678),
            d: seed.wrapping_mul(31),
            e: seed ^ 0xDEAD_BEEF,
        }
    }
}

macro_rules! impl_benches {
    ($name:ident, $mod:ident) => {
        mod $name {
            use super::Bencher;
            use super::Elem;
            use super::CAP;
            use super::FILL;
            use crate::$mod::Cursor;
            use crate::$mod::LinkedList;

            fn fill(n: usize) -> LinkedList<Elem> {
                let mut l = LinkedList::new();
                for i in 0..n {
                    l.push_back(Elem::new(i as u64));
                }
                l
            }
            
            #[divan::bench]
            fn lru_workload(bencher: Bencher) {
                const OPS: usize = CAP * 8;
                let bencher = bencher.counter(OPS);
                bencher
                    .with_inputs(|| {
                        let mut l = LinkedList::new();
                        let mut table: Vec<Option<Cursor<Elem>>> = vec![None; CAP * 2];
                        for i in 0..CAP {
                            table[i] = Some(l.push_back(Elem::new(i as u64)));
                        }
                        (l, table)
                    })
                    .bench_values(|(mut l, mut table)| {
                        let mut state: u64 = 0x9E37_79B9_7F4A_7C15;
                        for _ in 0..OPS {
                            state = state
                                .wrapping_mul(6_364_136_223_846_793_005)
                                .wrapping_add(1_442_695_040_888_963_407);
                            let key = ((state >> 33) as usize) % (CAP * 2);
                            if let Some(c) = table[key] {
                                unsafe { c.move_to_back(&mut l) };
                            } else {
                                let evicted = l.pop_front();
                                let evicted = unsafe { evicted.unwrap_unchecked() };
                                table[evicted.a as usize] = None;
                                table[key] = Some(l.push_back(Elem::new(key as u64)));
                            }
                        }
                    });
            }
            
            #[divan::bench]
            fn evict_insert_cycle(bencher: Bencher) {
                let bencher = bencher.counter(CAP);
                bencher.with_inputs(|| fill(CAP)).bench_values(|mut l| {
                    for i in 0..CAP {
                        let _ = l.pop_front();
                        l.push_back(Elem::new(i as u64));
                    }
                });
            }
            
            #[divan::bench]
            fn move_to_back(bencher: Bencher) {
                let bencher = bencher.counter(CAP);
                bencher
                    .with_inputs(|| {
                        let l = fill(CAP);
                        let cursors: Vec<Cursor<Elem>> = unsafe { l.iter().collect() };
                        (l, cursors)
                    })
                    .bench_values(|(mut l, cursors)| {
                        for c in cursors {
                            unsafe { c.move_to_back(&mut l) };
                        }
                    });
            }
            
            #[divan::bench]
            fn pop_front_all(bencher: Bencher) {
                let bencher = bencher.counter(FILL);
                bencher.with_inputs(|| fill(FILL)).bench_values(|mut l| {
                    while let Some(e) = l.pop_front() {
                        divan::black_box(e.a);
                    }
                });
            }
            
            #[divan::bench]
            fn iterate(bencher: Bencher) {
                let bencher = bencher.counter(FILL);
                bencher.with_inputs(|| fill(FILL)).bench_values(|l| {
                    let mut s: u64 = 0;
                    unsafe {
                        for c in l.iter() {
                            s = s.wrapping_add(c.element().a);
                        }
                    }
                    divan::black_box(s);
                });
            }
        }
    };
}

impl_benches!(upstream, old);
impl_benches!(pr_version, new);

fn main() {
    divan::main();
}

Results (about ~2.7-2.8 times faster):

❯ cargo bench -- lru_workload                                                                                                                  
    Finished `bench` profile [optimized] target(s) in 0.04s                                                                                    
     Running bench.rs (target\release\deps\bench-eb9d257623e7ec19.exe)
Timer precision: 100 ns
bench               fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ pr_version                     │               │               │               │         │
│  ╰─ lru_workload  60.99 µs      │ 129.8 µs      │ 64.09 µs      │ 67.2 µs       │ 100     │ 100
│                   134.2 Mitem/s │ 63.06 Mitem/s │ 127.8 Mitem/s │ 121.8 Mitem/s │         │
╰─ upstream                       │               │               │               │         │
   ╰─ lru_workload  227 µs        │ 513.8 µs      │ 283.7 µs      │ 302.8 µs      │ 100     │ 100
                    36.07 Mitem/s │ 15.94 Mitem/s │ 28.86 Mitem/s │ 27.05 Mitem/s │         │

❯ cargo bench -- lru_workload                                                                                                                  
    Finished `bench` profile [optimized] target(s) in 0.04s                                                                                    
     Running bench.rs (target\release\deps\bench-eb9d257623e7ec19.exe)
Timer precision: 100 ns
bench               fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ pr_version                     │               │               │               │         │
│  ╰─ lru_workload  107.9 µs      │ 179.9 µs      │ 135.7 µs      │ 135.8 µs      │ 100     │ 100
│                   75.85 Mitem/s │ 45.51 Mitem/s │ 60.34 Mitem/s │ 60.28 Mitem/s │         │
╰─ upstream                       │               │               │               │         │
   ╰─ lru_workload  234.2 µs      │ 437.5 µs      │ 284.6 µs      │ 292.9 µs      │ 100     │ 100
                    34.96 Mitem/s │ 18.72 Mitem/s │ 28.77 Mitem/s │ 27.96 Mitem/s │         │

❯ cargo bench -- lru_workload                                                                                                                  
    Finished `bench` profile [optimized] target(s) in 0.03s                                                                                    
     Running bench.rs (target\release\deps\bench-eb9d257623e7ec19.exe)
Timer precision: 100 ns
bench               fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ pr_version                     │               │               │               │         │
│  ╰─ lru_workload  104.2 µs      │ 139 µs        │ 110.1 µs      │ 112.1 µs      │ 100     │ 100
│                   78.54 Mitem/s │ 58.89 Mitem/s │ 74.37 Mitem/s │ 73.01 Mitem/s │         │
╰─ upstream                       │               │               │               │         │
   ╰─ lru_workload  242.2 µs      │ 599.2 µs      │ 357.8 µs      │ 362.1 µs      │ 100     │ 100
                    33.8 Mitem/s  │ 13.66 Mitem/s │ 22.89 Mitem/s │ 22.61 Mitem/s │         │

❯ cargo bench -- lru_workload                                                                                                                  
    Finished `bench` profile [optimized] target(s) in 0.04s                                                                                    
     Running bench.rs (target\release\deps\bench-eb9d257623e7ec19.exe)
Timer precision: 100 ns
bench               fastest       │ slowest       │ median        │ mean          │ samples │ iters
├─ pr_version                     │               │               │               │         │
│  ╰─ lru_workload  97.69 µs      │ 134.8 µs      │ 107.2 µs      │ 108 µs        │ 100     │ 100
│                   83.84 Mitem/s │ 60.72 Mitem/s │ 76.34 Mitem/s │ 75.84 Mitem/s │         │
╰─ upstream                       │               │               │               │         │
   ╰─ lru_workload  215.8 µs      │ 489.6 µs      │ 246.6 µs      │ 288.1 µs      │ 100     │ 100
                    37.94 Mitem/s │ 16.72 Mitem/s │ 33.2 Mitem/s  │ 28.43 Mitem/s │         │

@awolverp

awolverp commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Thanks 🔥

@chirizxc

chirizxc commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

I'll double-check all the SAFETY comments again a little later

@awolverp

awolverp commented Aug 4, 2026

Copy link
Copy Markdown
Owner

Aha OK

@awolverp

awolverp commented Aug 4, 2026

Copy link
Copy Markdown
Owner

This implementation is really good. I checked it.

But I think there's an issue
It's using an internal free list without any bound. I think it can consume a lot of memory because it won't free its memory until destruction.

We should remove the free list, or set a maximum length for it

@chirizxc

chirizxc commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

This implementation is really good. I checked it.

But I think there's an issue It's using an internal free list without any bound. I think it can consume a lot of memory because it won't free its memory until destruction.

We should remove the free list, or set a maximum length for it

Yes, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants