Skip to main content

embed_slist/
lib.rs

1#![allow(rustdoc::redundant_explicit_links)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![cfg_attr(docsrs, allow(unused_attributes))]
4#![cfg_attr(not(feature = "std"), no_std)]
5
6//! An intrusive singly linked list implementation.
7//!
8//! This module provides `SLinkedList`, a singly linked list optimized for FIFO (First-In, First-Out)
9//! queue-like behavior. Elements embed the list nodes themselves, offering memory efficiency
10//! and explicit control over allocation.
11//!
12//! # Features
13//! - O(1) push to back and pop from front.
14//! - Generic over pointer types (`Box`, `Arc`, `NonNull`, raw pointers).
15//!
16//! # Example
17//!
18//! ```rust
19//! use embed_slist::{SLinkedList, SListItem, SListNode};
20//! use core::cell::UnsafeCell;
21//! use std::sync::Arc;
22//! use core::ptr::NonNull;
23//!
24//! struct MyTask {
25//!     priority: u8,
26//!     description: String,
27//!     node: UnsafeCell<SListNode<MyTask, ()>>,
28//! }
29//!
30//! impl MyTask {
31//!     fn new(priority: u8, desc: &str) -> Self {
32//!         MyTask {
33//!             priority,
34//!             description: desc.to_string(),
35//!             node: UnsafeCell::new(SListNode::default()),
36//!         }
37//!     }
38//! }
39//!
40//! unsafe impl SListItem<()> for MyTask {
41//!     fn get_node(&self) -> &mut SListNode<Self, ()> {
42//!         unsafe { &mut *self.node.get() }
43//!     }
44//! }
45//!
46//! // Using Box<T> (owned pointers)
47//! {
48//!     let mut task_queue = SLinkedList::<Box<MyTask>, ()>::new();
49//!     task_queue.push_back(Box::new(MyTask::new(1, "Handle user login")));
50//!     task_queue.push_back(Box::new(MyTask::new(2, "Process analytics data")));
51//!     task_queue.push_back(Box::new(MyTask::new(1, "Send welcome email")));
52//!     assert_eq!(task_queue.len(), 3);
53//!     assert_eq!(task_queue.pop_front().unwrap().description, "Handle user login");
54//!     assert_eq!(task_queue.pop_front().unwrap().description, "Process analytics data");
55//!     assert_eq!(task_queue.pop_front().unwrap().description, "Send welcome email");
56//!     assert!(task_queue.is_empty());
57//! }
58//!
59//! // Using Arc<T> (shared ownership)
60//! {
61//!     let mut task_queue = SLinkedList::<Arc<MyTask>, ()>::new();
62//!     task_queue.push_back(Arc::new(MyTask::new(1, "Handle user login")));
63//!     task_queue.push_back(Arc::new(MyTask::new(2, "Process analytics data")));
64//!     task_queue.push_back(Arc::new(MyTask::new(1, "Send welcome email")));
65//!     assert_eq!(task_queue.len(), 3);
66//!     assert_eq!(task_queue.pop_front().unwrap().description, "Handle user login");
67//!     assert_eq!(task_queue.pop_front().unwrap().description, "Process analytics data");
68//!     assert_eq!(task_queue.pop_front().unwrap().description, "Send welcome email");
69//!     assert!(task_queue.is_empty());
70//! }
71//!
72//! // Using NonNull<T> (raw pointers without ownership)
73//! {
74//!     let mut task_queue = SLinkedList::<NonNull<MyTask>, ()>::new();
75//!     let task1 = Box::into_raw(Box::new(MyTask::new(1, "Handle user login")));
76//!     let task2 = Box::into_raw(Box::new(MyTask::new(2, "Process analytics data")));
77//!     let task3 = Box::into_raw(Box::new(MyTask::new(1, "Send welcome email")));
78//!     task_queue.push_back(unsafe{NonNull::new_unchecked(task1)});
79//!     task_queue.push_back(unsafe{NonNull::new_unchecked(task2)});
80//!     task_queue.push_back(unsafe{NonNull::new_unchecked(task3)});
81//!     assert_eq!(task_queue.len(), 3);
82//!     assert_eq!(unsafe { &task_queue.pop_front().unwrap().as_ref().description }, "Handle user login");
83//!     assert_eq!(unsafe { &task_queue.pop_front().unwrap().as_ref().description }, "Process analytics data");
84//!     assert_eq!(unsafe { &task_queue.pop_front().unwrap().as_ref().description }, "Send welcome email");
85//!     assert!(task_queue.is_empty());
86//!     unsafe{
87//!         let _ = Box::from_raw(task1);
88//!         let _ = Box::from_raw(task2);
89//!         let _ = Box::from_raw(task3);
90//!     }
91//! }
92//! ```
93
94extern crate alloc;
95#[cfg(any(feature = "std", test))]
96extern crate std;
97
98use core::fmt;
99use core::marker::PhantomData;
100use core::mem;
101use core::ptr::{self, null};
102use pointers::Pointer;
103
104/// A trait to return internal mutable SListNode for specified list.
105///
106/// The tag is used to distinguish different SListNodes within the same item.
107/// For only one ownership, you can use `()`.
108///
109/// # Safety
110/// Implementors must ensure `get_node` returns a valid reference to the `SListNode`
111/// embedded within `Self`. Users must use `UnsafeCell` to hold `SListNode` to support
112/// interior mutability required by list operations.
113pub unsafe trait SListItem<Tag>: Sized {
114    fn get_node(&self) -> &mut SListNode<Self, Tag>;
115}
116
117/// The node structure that must be embedded in items to be stored in a `SLinkedList`.
118///
119#[repr(C)]
120pub struct SListNode<T: Sized, Tag> {
121    next: *const T,
122    _phan: PhantomData<fn(&Tag)>,
123}
124
125unsafe impl<T, Tag> Send for SListNode<T, Tag> {}
126
127impl<T: SListItem<Tag>, Tag> SListNode<T, Tag> {}
128
129impl<T, Tag> Default for SListNode<T, Tag> {
130    #[inline(always)]
131    fn default() -> Self {
132        Self { next: null(), _phan: Default::default() }
133    }
134}
135
136impl<T: SListItem<Tag> + fmt::Debug, Tag> fmt::Debug for SListNode<T, Tag> {
137    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
138        write!(f, "(")?;
139        if !self.next.is_null() {
140            write!(f, "next: {:p} ", self.next)?;
141        } else {
142            write!(f, "next: none ")?;
143        }
144        write!(f, ")")
145    }
146}
147
148/// A singly linked list with head and tail pointers (FIFO queue).
149///
150/// Supports O(1) push to back and pop from front.
151#[repr(C)]
152pub struct SLinkedList<P, Tag>
153where
154    P: Pointer,
155    P::Target: SListItem<Tag>,
156{
157    length: usize,
158    head: *const P::Target,
159    tail: *const P::Target,
160    _phan: PhantomData<fn(&Tag)>,
161}
162
163unsafe impl<P, Tag> Send for SLinkedList<P, Tag>
164where
165    P: Pointer,
166    P::Target: SListItem<Tag>,
167{
168}
169
170impl<P: fmt::Debug, Tag> fmt::Debug for SLinkedList<P, Tag>
171where
172    P: Pointer,
173    P::Target: SListItem<Tag>,
174{
175    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
176        write!(f, "{{ length: {} ", self.length)?;
177        if !self.head.is_null() {
178            write!(f, "head: {:?} ", self.head)?;
179        } else {
180            write!(f, "head: none ")?;
181        }
182        if !self.tail.is_null() {
183            write!(f, "tail: {:?} ", self.tail)?;
184        } else {
185            write!(f, "tail: none ")?;
186        }
187        write!(f, "}}")
188    }
189}
190
191impl<P, Tag> SLinkedList<P, Tag>
192where
193    P: Pointer,
194    P::Target: SListItem<Tag>,
195{
196    /// Creates a new, empty singly linked list.
197    #[inline(always)]
198    pub fn new() -> Self {
199        SLinkedList { length: 0, head: null(), tail: null(), _phan: Default::default() }
200    }
201
202    /// Clears the list, dropping all of its elements if the pointer type `P` owns them.
203    #[inline]
204    pub fn clear(&mut self) {
205        // By repeatedly popping from the front, we drop each element.
206        // If P is an owned pointer (like Box), the element is dropped.
207        // If P is a raw pointer, it's a no-op, but the list is still emptied.
208        while self.pop_front().is_some() {}
209    }
210
211    /// Returns the length of the list as `usize`.
212    #[inline(always)]
213    pub fn len(&self) -> usize {
214        self.length
215    }
216
217    /// Returns `true` if the list contains no elements.
218    #[inline(always)]
219    pub fn is_empty(&self) -> bool {
220        self.length == 0
221    }
222
223    /// Appends an element to the back of the list (FIFO: enqueue).
224    #[inline]
225    pub fn push_back(&mut self, item: P) {
226        let node = item.as_ref().get_node();
227        node.next = null();
228        let ptr = item.into_raw();
229        if !self.tail.is_null() {
230            // List is not empty, update current tail's next
231            unsafe {
232                (*self.tail).get_node().next = ptr;
233            }
234        } else {
235            // List is empty
236            self.head = ptr;
237        }
238        self.tail = ptr;
239        self.length += 1;
240    }
241
242    /// Pushes an element to the front of the list.
243    #[inline]
244    pub fn push_front(&mut self, item: P) {
245        let ptr = item.into_raw();
246        let node = unsafe { (*ptr).get_node() };
247        node.next = self.head;
248        if self.head.is_null() {
249            // List was empty
250            self.tail = ptr;
251        }
252        self.head = ptr;
253        self.length += 1;
254    }
255
256    /// Removes the first element and returns it (FIFO: dequeue).
257    pub fn pop_front(&mut self) -> Option<P> {
258        if !self.head.is_null() {
259            let head_ptr = self.head;
260            let node = unsafe { (*head_ptr).get_node() };
261            let next_ptr = node.next;
262
263            // Update head to next
264            self.head = next_ptr;
265
266            // If head became null (list empty), update tail to null too
267            if self.head.is_null() {
268                self.tail = null();
269            }
270
271            // Clean up the removed node's next pointer
272            node.next = null();
273            self.length -= 1;
274
275            Some(unsafe { P::from_raw(head_ptr) })
276        } else {
277            None
278        }
279    }
280
281    /// Returns a reference to the front element.
282    #[inline]
283    pub fn get_front(&self) -> Option<&P::Target> {
284        if !self.head.is_null() { unsafe { Some(&(*self.head)) } } else { None }
285    }
286
287    /// Returns a reference to the back element.
288    #[inline]
289    pub fn get_back(&self) -> Option<&P::Target> {
290        if !self.tail.is_null() { unsafe { Some(&(*self.tail)) } } else { None }
291    }
292
293    /// Checks if the given node is the head of the list.
294    #[inline(always)]
295    pub fn is_front(&self, node: &P::Target) -> bool {
296        if self.head.is_null() { false } else { ptr::eq(self.head, node) }
297    }
298
299    /// Returns an iterator over the list (borrowed).
300    ///
301    /// # NOTE
302    ///
303    /// If you plan on turn the raw pointer to owned, use drain instead
304    ///
305    /// # Safety
306    ///
307    /// The caller must ensure that the list is not modified in a way that can
308    /// invalidate internal pointers (such as removing elements or dropping
309    /// items) for the duration of the iterator's use.
310    #[inline(always)]
311    pub fn iter<'a>(&'a self) -> SLinkedListIterator<'a, P, Tag> {
312        SLinkedListIterator { list: self, cur: null() }
313    }
314
315    /// Returns a draining iterator that removes items from the list.
316    #[inline(always)]
317    pub fn drain<'a>(&'a mut self) -> SLinkedListDrainer<'a, P, Tag> {
318        SLinkedListDrainer { list: self }
319    }
320}
321
322impl<P, Tag> Drop for SLinkedList<P, Tag>
323where
324    P: Pointer,
325    P::Target: SListItem<Tag>,
326{
327    fn drop(&mut self) {
328        if mem::needs_drop::<P>() {
329            self.drain().for_each(drop);
330        }
331    }
332}
333
334pub struct SLinkedListIterator<'a, P, Tag>
335where
336    P: Pointer,
337    P::Target: SListItem<Tag>,
338{
339    list: &'a SLinkedList<P, Tag>,
340    cur: *const P::Target,
341}
342
343unsafe impl<'a, P, Tag> Send for SLinkedListIterator<'a, P, Tag>
344where
345    P: Pointer,
346    P::Target: SListItem<Tag>,
347{
348}
349
350impl<'a, P, Tag> Iterator for SLinkedListIterator<'a, P, Tag>
351where
352    P: Pointer,
353    P::Target: SListItem<Tag>,
354{
355    type Item = &'a P::Target;
356
357    #[inline]
358    fn next(&mut self) -> Option<Self::Item> {
359        if !self.cur.is_null() {
360            let next = unsafe { (*self.cur).get_node().next };
361            if !next.is_null() {
362                self.cur = next;
363            } else {
364                return None;
365            }
366        } else {
367            if !self.list.head.is_null() {
368                self.cur = self.list.head;
369            } else {
370                return None;
371            }
372        }
373        unsafe { Some(&(*self.cur)) }
374    }
375}
376
377pub struct SLinkedListDrainer<'a, P, Tag>
378where
379    P: Pointer,
380    P::Target: SListItem<Tag>,
381{
382    list: &'a mut SLinkedList<P, Tag>,
383}
384
385unsafe impl<'a, P, Tag> Send for SLinkedListDrainer<'a, P, Tag>
386where
387    P: Pointer,
388    P::Target: SListItem<Tag>,
389{
390}
391
392impl<'a, P, Tag> Iterator for SLinkedListDrainer<'a, P, Tag>
393where
394    P: Pointer,
395    P::Target: SListItem<Tag>,
396{
397    type Item = P;
398
399    #[inline]
400    fn next(&mut self) -> Option<P> {
401        self.list.pop_front()
402    }
403}
404
405#[cfg(test)]
406mod tests {
407    use super::*;
408    use alloc::boxed::Box;
409    use std::cell::UnsafeCell;
410    use std::sync::atomic::{AtomicUsize, Ordering};
411
412    pub struct TestTag;
413
414    #[derive(Debug)]
415    pub struct TestNode {
416        pub value: i64,
417        pub node: UnsafeCell<SListNode<Self, TestTag>>,
418    }
419
420    static ACTIVE_NODE_COUNT: AtomicUsize = AtomicUsize::new(0);
421
422    impl Drop for TestNode {
423        fn drop(&mut self) {
424            ACTIVE_NODE_COUNT.fetch_sub(1, Ordering::SeqCst);
425        }
426    }
427
428    unsafe impl Send for TestNode {}
429
430    unsafe impl SListItem<TestTag> for TestNode {
431        fn get_node(&self) -> &mut SListNode<Self, TestTag> {
432            unsafe { &mut *self.node.get() }
433        }
434    }
435
436    fn new_node(v: i64) -> TestNode {
437        ACTIVE_NODE_COUNT.fetch_add(1, Ordering::SeqCst);
438        TestNode { value: v, node: UnsafeCell::new(SListNode::default()) }
439    }
440
441    #[test]
442    fn test_push_back_pop_front_box() {
443        ACTIVE_NODE_COUNT.store(0, Ordering::SeqCst);
444        let mut l = SLinkedList::<Box<TestNode>, TestTag>::new();
445
446        let node1 = Box::new(new_node(1));
447        l.push_back(node1);
448
449        let node2 = Box::new(new_node(2));
450        l.push_back(node2);
451
452        let node3 = Box::new(new_node(3));
453        l.push_back(node3);
454
455        assert_eq!(3, l.len());
456        assert_eq!(ACTIVE_NODE_COUNT.load(Ordering::SeqCst), 3);
457
458        // Test iterator
459        let mut iter = l.iter();
460        assert_eq!(iter.next().unwrap().value, 1);
461        assert_eq!(iter.next().unwrap().value, 2);
462        assert_eq!(iter.next().unwrap().value, 3);
463        assert!(iter.next().is_none());
464
465        // Test pop_front (FIFO)
466        let n1 = l.pop_front();
467        assert!(n1.is_some());
468        assert_eq!(n1.unwrap().value, 1);
469        assert_eq!(l.len(), 2);
470
471        let n2 = l.pop_front();
472        assert!(n2.is_some());
473        assert_eq!(n2.unwrap().value, 2);
474        assert_eq!(l.len(), 1);
475
476        let n3 = l.pop_front();
477        assert!(n3.is_some());
478        assert_eq!(n3.unwrap().value, 3);
479        assert_eq!(l.len(), 0);
480
481        assert!(l.pop_front().is_none());
482        assert_eq!(ACTIVE_NODE_COUNT.load(Ordering::SeqCst), 0);
483    }
484
485    #[test]
486    fn test_push_front_box() {
487        ACTIVE_NODE_COUNT.store(0, Ordering::SeqCst);
488        let mut l = SLinkedList::<Box<TestNode>, TestTag>::new();
489
490        let node1 = Box::new(new_node(1));
491        l.push_front(node1); // List: [1]
492
493        let node2 = Box::new(new_node(2));
494        l.push_front(node2); // List: [2, 1]
495
496        let node3 = Box::new(new_node(3));
497        l.push_front(node3); // List: [3, 2, 1]
498
499        assert_eq!(3, l.len());
500        assert_eq!(ACTIVE_NODE_COUNT.load(Ordering::SeqCst), 3);
501
502        // Test iterator (should be 3, 2, 1)
503        let mut iter = l.iter();
504        assert_eq!(iter.next().unwrap().value, 3);
505        assert_eq!(iter.next().unwrap().value, 2);
506        assert_eq!(iter.next().unwrap().value, 1);
507        assert!(iter.next().is_none());
508
509        // Test pop_front (FIFO)
510        let n1 = l.pop_front();
511        assert!(n1.is_some());
512        assert_eq!(n1.unwrap().value, 3);
513        assert_eq!(l.len(), 2);
514
515        let n2 = l.pop_front();
516        assert!(n2.is_some());
517        assert_eq!(n2.unwrap().value, 2);
518        assert_eq!(l.len(), 1);
519
520        let n3 = l.pop_front();
521        assert!(n3.is_some());
522        assert_eq!(n3.unwrap().value, 1);
523        assert_eq!(l.len(), 0);
524
525        assert!(l.pop_front().is_none());
526        assert_eq!(ACTIVE_NODE_COUNT.load(Ordering::SeqCst), 0);
527    }
528
529    #[test]
530    fn test_drain() {
531        ACTIVE_NODE_COUNT.store(0, Ordering::SeqCst);
532        let mut l = SLinkedList::<Box<TestNode>, TestTag>::new();
533
534        l.push_back(Box::new(new_node(10)));
535        l.push_back(Box::new(new_node(20)));
536        l.push_back(Box::new(new_node(30)));
537
538        assert_eq!(l.len(), 3);
539        assert_eq!(ACTIVE_NODE_COUNT.load(Ordering::SeqCst), 3);
540
541        {
542            let mut drain = l.drain();
543            assert_eq!(drain.next().unwrap().value, 10);
544            assert_eq!(drain.next().unwrap().value, 20);
545            assert_eq!(drain.next().unwrap().value, 30);
546            assert!(drain.next().is_none());
547        }
548
549        assert_eq!(l.len(), 0);
550        assert_eq!(ACTIVE_NODE_COUNT.load(Ordering::SeqCst), 0);
551    }
552
553    #[test]
554    fn test_clear() {
555        ACTIVE_NODE_COUNT.store(0, Ordering::SeqCst);
556        let mut l = SLinkedList::<Box<TestNode>, TestTag>::new();
557
558        l.push_back(Box::new(new_node(1)));
559        l.push_back(Box::new(new_node(2)));
560        assert_eq!(l.len(), 2);
561        assert_eq!(ACTIVE_NODE_COUNT.load(Ordering::SeqCst), 2);
562
563        l.clear();
564
565        assert!(l.is_empty());
566        assert_eq!(l.len(), 0);
567        assert!(l.get_front().is_none());
568        assert!(l.get_back().is_none());
569        assert_eq!(ACTIVE_NODE_COUNT.load(Ordering::SeqCst), 0);
570
571        // Can still push to the list
572        l.push_back(Box::new(new_node(3)));
573        assert_eq!(l.len(), 1);
574        assert_eq!(ACTIVE_NODE_COUNT.load(Ordering::SeqCst), 1);
575    }
576}