Skip to main content

embed_collections/
slist.rs

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