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