Skip to main content

satellite_collections/generic/
fixed_list.rs

1//! Fixed-capacity list implementation for allocation-free environments.
2//!
3//! This module provides [`FixedList`], a contiguous array-backed list with stack-like
4//! `push`/`pop` semantics and compile-time capacity bounds.
5
6use crate::generic::push_pop::{PushPopCollection, PushPopError};
7
8#[cfg(feature = "borsh")]
9use borsh::{BorshDeserialize, BorshSerialize};
10
11/// Error type for [`FixedList`] operations.
12#[derive(Debug)]
13pub enum FixedListError {
14    /// The list has reached its maximum capacity.
15    Full,
16}
17
18/// A fixed-capacity list backed by a contiguous array.
19///
20/// `FixedList` provides stack-like operations (`push`/`pop`) with compile-time capacity bounds.
21/// It maintains insertion order and allows efficient element access via slices.
22///
23/// # Type Parameters
24///
25/// * `T` - The element type. Must implement `Default + Copy` for initialization.
26/// * `SIZE` - The maximum number of elements the list can hold (compile-time constant).
27///
28/// # Examples
29///
30/// ```rust
31/// use satellite_bitcoin::generic::fixed_list::FixedList;
32///
33/// let mut list: FixedList<u32, 4> = FixedList::new();
34///
35/// list.push(10).unwrap();
36/// list.push(20).unwrap();
37/// assert_eq!(list.len(), 2);
38/// assert_eq!(list.as_slice(), &[10, 20]);
39///
40/// assert_eq!(list.pop(), Some(20));
41/// assert_eq!(list.len(), 1);
42/// ```
43///
44/// # Memory Layout
45///
46/// The list stores elements in a contiguous array followed by a length field.
47/// Only the first `len` elements are considered valid; the rest contain default values.
48#[derive(Debug, Clone)]
49#[cfg_attr(feature = "borsh", derive(BorshSerialize, BorshDeserialize))]
50pub struct FixedList<T, const SIZE: usize> {
51    items: [T; SIZE],
52    len: usize,
53}
54
55impl<T: Default + Clone, const SIZE: usize> Default for FixedList<T, SIZE> {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl<T: Default + Clone, const SIZE: usize> FixedList<T, SIZE> {
62    /// Creates a new, empty `FixedList`.
63    ///
64    /// All elements are initialized to their default values, but only the first `len`
65    /// elements are considered valid.
66    ///
67    /// # Examples
68    ///
69    /// ```rust
70    /// use satellite_bitcoin::generic::fixed_list::FixedList;
71    ///
72    /// let list: FixedList<u32, 10> = FixedList::new();
73    /// assert!(list.is_empty());
74    /// assert_eq!(list.len(), 0);
75    /// ```
76    pub fn new() -> Self {
77        Self {
78            items: core::array::from_fn(|_| T::default()),
79            len: 0,
80        }
81    }
82
83    /// Creates a `FixedList` from a slice, copying elements up to the capacity.
84    ///
85    /// If the slice is longer than the capacity, only the first `SIZE` elements are copied.
86    ///
87    /// # Examples
88    ///
89    /// ```rust
90    /// use satellite_bitcoin::generic::fixed_list::FixedList;
91    ///
92    /// let data = [1, 2, 3];
93    /// let list: FixedList<i32, 5> = FixedList::from_slice(&data);
94    /// assert_eq!(list.len(), 3);
95    /// assert_eq!(list.as_slice(), &[1, 2, 3]);
96    /// ```
97    pub fn from_slice(slice: &[T]) -> Self {
98        let mut list = Self::new();
99        list.copy_from_slice(slice);
100        list
101    }
102
103    /// Creates a `FixedList` from an iterator, taking up to `SIZE` elements.
104    ///
105    /// # Examples
106    ///
107    /// ```rust
108    /// use satellite_bitcoin::generic::fixed_list::FixedList;
109    ///
110    /// let list: FixedList<u32, 3> = FixedList::from_iter(0..10);
111    /// assert_eq!(list.len(), 3);
112    /// assert_eq!(list.as_slice(), &[0, 1, 2]);
113    /// ```
114    pub fn from_iter<I: Iterator<Item = T>>(iter: I) -> Self {
115        let mut list = Self::new();
116
117        for item in iter.take(SIZE) {
118            let _ = list.push(item);
119        }
120
121        list
122    }
123
124    /// Returns the number of elements in the list.
125    ///
126    /// # Examples
127    ///
128    /// ```rust
129    /// use satellite_bitcoin::generic::fixed_list::FixedList;
130    ///
131    /// let mut list: FixedList<u32, 5> = FixedList::new();
132    /// assert_eq!(list.len(), 0);
133    ///
134    /// list.push(42).unwrap();
135    /// assert_eq!(list.len(), 1);
136    /// ```
137    pub fn len(&self) -> usize {
138        self.len
139    }
140
141    /// Returns `true` if the list contains no elements.
142    ///
143    /// # Examples
144    ///
145    /// ```rust
146    /// use satellite_bitcoin::generic::fixed_list::FixedList;
147    ///
148    /// let mut list: FixedList<u32, 5> = FixedList::new();
149    /// assert!(list.is_empty());
150    ///
151    /// list.push(42).unwrap();
152    /// assert!(!list.is_empty());
153    /// ```
154    pub fn is_empty(&self) -> bool {
155        self.len == 0
156    }
157
158    /// Returns an iterator over the elements in the list.
159    ///
160    /// # Examples
161    ///
162    /// ```rust
163    /// use satellite_bitcoin::generic::fixed_list::FixedList;
164    ///
165    /// let mut list: FixedList<u32, 5> = FixedList::new();
166    /// list.push(1).unwrap();
167    /// list.push(2).unwrap();
168    ///
169    /// let collected: Vec<_> = list.iter().copied().collect();
170    /// assert_eq!(collected, vec![1, 2]);
171    /// ```
172    pub fn iter(&self) -> impl Iterator<Item = &T> + '_ {
173        self.items[..self.len].iter()
174    }
175
176    /// Returns a mutable iterator over the elements in the list.
177    ///
178    /// # Examples
179    ///
180    /// ```rust
181    /// use satellite_bitcoin::generic::fixed_list::FixedList;
182    ///
183    /// let mut list: FixedList<u32, 5> = FixedList::new();
184    /// list.push(1).unwrap();
185    /// list.push(2).unwrap();
186    ///
187    /// for item in list.iter_mut() {
188    ///     *item *= 10;
189    /// }
190    /// assert_eq!(list.as_slice(), &[10, 20]);
191    /// ```
192    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> + '_ {
193        self.items[..self.len].iter_mut()
194    }
195
196    /// Appends an element to the back of the list.
197    ///
198    /// # Errors
199    ///
200    /// Returns [`FixedListError::Full`] if the list is at capacity.
201    ///
202    /// # Examples
203    ///
204    /// ```rust
205    /// use satellite_bitcoin::generic::fixed_list::FixedList;
206    ///
207    /// let mut list: FixedList<u32, 2> = FixedList::new();
208    /// assert!(list.push(1).is_ok());
209    /// assert!(list.push(2).is_ok());
210    /// assert!(list.push(3).is_err()); // List is full
211    /// ```
212    pub fn push(&mut self, item: T) -> Result<(), FixedListError> {
213        if self.len >= SIZE {
214            return Err(FixedListError::Full);
215        }
216
217        self.items[self.len] = item;
218        self.len += 1;
219
220        Ok(())
221    }
222
223    /// Removes and returns the last element, or `None` if the list is empty.
224    ///
225    /// # Examples
226    ///
227    /// ```rust
228    /// use satellite_bitcoin::generic::fixed_list::FixedList;
229    ///
230    /// let mut list: FixedList<u32, 5> = FixedList::new();
231    /// assert_eq!(list.pop(), None);
232    ///
233    /// list.push(42).unwrap();
234    /// assert_eq!(list.pop(), Some(42));
235    /// ```
236    pub fn pop(&mut self) -> Option<T> {
237        if self.len > 0 {
238            self.len -= 1;
239            Some(self.items[self.len].clone())
240        } else {
241            None
242        }
243    }
244
245    /// Returns a slice containing all elements in the list.
246    ///
247    /// # Examples
248    ///
249    /// ```rust
250    /// use satellite_bitcoin::generic::fixed_list::FixedList;
251    ///
252    /// let mut list: FixedList<u32, 5> = FixedList::new();
253    /// list.push(1).unwrap();
254    /// list.push(2).unwrap();
255    ///
256    /// let slice = list.as_slice();
257    /// assert_eq!(slice, &[1, 2]);
258    /// ```
259    pub fn as_slice(&self) -> &[T] {
260        &self.items[..self.len]
261    }
262
263    /// Returns a mutable slice containing all elements in the list.
264    ///
265    /// # Examples
266    ///
267    /// ```rust
268    /// use satellite_bitcoin::generic::fixed_list::FixedList;
269    ///
270    /// let mut list: FixedList<u32, 5> = FixedList::new();
271    /// list.push(1).unwrap();
272    /// list.push(2).unwrap();
273    ///
274    /// let slice = list.as_mut_slice();
275    /// slice[0] = 10;
276    /// assert_eq!(list.as_slice(), &[10, 2]);
277    /// ```
278    pub fn as_mut_slice(&mut self) -> &mut [T] {
279        &mut self.items[..self.len]
280    }
281
282    /// Returns `true` if the list contains an element equal to `item`.
283    ///
284    /// This method is generic over `Q` as long as `T: PartialEq<Q>`, allowing
285    /// lookups by a different but comparable type.
286    ///
287    /// # Examples
288    ///
289    /// ```rust
290    /// use satellite_bitcoin::generic::fixed_list::FixedList;
291    ///
292    /// let mut list: FixedList<u32, 5> = FixedList::new();
293    /// list.push(1).unwrap();
294    /// list.push(2).unwrap();
295    /// assert!(list.contains(&1));
296    /// assert!(!list.contains(&3));
297    /// ```
298    pub fn contains<Q>(&self, item: &Q) -> bool
299    where
300        T: PartialEq<Q>,
301    {
302        self.iter().any(|i| i == item)
303    }
304
305    /// Copies elements from a slice into the list, replacing existing contents.
306    ///
307    /// The list length is updated to match the slice length (up to capacity).
308    ///
309    /// # Examples
310    ///
311    /// ```rust
312    /// use satellite_bitcoin::generic::fixed_list::FixedList;
313    ///
314    /// let mut list: FixedList<u32, 5> = FixedList::new();
315    /// let data = [10, 20, 30];
316    /// list.copy_from_slice(&data);
317    ///
318    /// assert_eq!(list.len(), 3);
319    /// assert_eq!(list.as_slice(), &[10, 20, 30]);
320    /// ```
321    pub fn copy_from_slice(&mut self, slice: &[T])
322    where
323        T: Clone,
324    {
325        self.len = slice.len();
326        self.items[..self.len].clone_from_slice(slice);
327    }
328}
329
330impl<T: Default + Copy, const SIZE: usize> PushPopCollection<T> for FixedList<T, SIZE> {
331    fn push(&mut self, item: T) -> Result<(), PushPopError> {
332        self.push(item).map_err(|_| PushPopError::Full)
333    }
334
335    fn pop(&mut self) -> Option<T> {
336        self.pop()
337    }
338
339    fn as_slice(&self) -> &[T] {
340        self.as_slice()
341    }
342
343    fn len(&self) -> usize {
344        self.len
345    }
346
347    fn max_size(&self) -> usize {
348        SIZE
349    }
350}
351
352// Conversions to/from FixedSet
353impl<T: Default + Clone + Copy + PartialEq, const SIZE: usize>
354    From<crate::generic::fixed_set::FixedSet<T, SIZE>> for FixedList<T, SIZE>
355{
356    fn from(set: crate::generic::fixed_set::FixedSet<T, SIZE>) -> Self {
357        let mut list = Self::new();
358        let slice = set.as_slice();
359        list.copy_from_slice(slice);
360        list
361    }
362}
363
364// From a slice (copies up to capacity)
365impl<T: Default + Clone, const SIZE: usize> From<&[T]> for FixedList<T, SIZE> {
366    fn from(slice: &[T]) -> Self {
367        // Use from_iter to enforce capacity capping safely.
368        Self::from_iter(slice.iter().cloned())
369    }
370}
371
372#[cfg(test)]
373mod from_slice_tests {
374    use super::*;
375
376    #[test]
377    fn list_from_slice_caps_to_capacity() {
378        let data = [1u32, 2, 3, 4, 5];
379        let list: FixedList<u32, 3> = FixedList::from(&data[..]);
380        assert_eq!(list.len(), 3);
381        assert_eq!(list.as_slice(), &[1, 2, 3]);
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388
389    #[test]
390    fn test_default_is_empty() {
391        let list = FixedList::<u32, 4>::default();
392        assert_eq!(list.len(), 0);
393        assert!(list.is_empty());
394    }
395
396    #[test]
397    fn test_push_and_len() {
398        let mut list = FixedList::<u32, 3>::new();
399        list.push(10).unwrap();
400        list.push(20).unwrap();
401        assert_eq!(list.len(), 2);
402        assert!(!list.is_empty());
403        assert_eq!(list.as_slice(), &[10, 20]);
404    }
405
406    #[test]
407    fn test_push_past_capacity() {
408        let mut list = FixedList::<u32, 2>::new();
409        list.push(1).unwrap();
410        list.push(2).unwrap();
411        list.push(3).unwrap_err(); // should be ignored
412        assert_eq!(list.len(), 2);
413        assert_eq!(list.as_slice(), &[1, 2]);
414    }
415
416    #[test]
417    fn test_pop() {
418        let mut list = FixedList::<u32, 2>::new();
419        assert_eq!(list.pop(), None);
420
421        list.push(100).unwrap();
422        list.push(200).unwrap();
423        assert_eq!(list.pop(), Some(200));
424        assert_eq!(list.pop(), Some(100));
425        assert_eq!(list.pop(), None);
426        assert!(list.is_empty());
427    }
428
429    #[test]
430    fn test_iter() {
431        let mut list = FixedList::<u32, 3>::new();
432        list.push(1).unwrap();
433        list.push(2).unwrap();
434        let collected: Vec<_> = list.iter().copied().collect();
435        assert_eq!(collected, vec![1, 2]);
436    }
437
438    #[test]
439    fn test_iter_mut() {
440        let mut list = FixedList::<u32, 3>::new();
441        list.push(1).unwrap();
442        list.push(2).unwrap();
443        for x in list.iter_mut() {
444            *x *= 10;
445        }
446        assert_eq!(list.as_slice(), &[10, 20]);
447    }
448
449    #[test]
450    fn test_as_slice_and_mut_slice() {
451        let mut list = FixedList::<u32, 4>::new();
452        list.push(42).unwrap();
453        list.push(99).unwrap();
454        let slice = list.as_slice();
455        assert_eq!(slice, &[42, 99]);
456
457        let mut_slice = list.as_mut_slice();
458        mut_slice[0] = 123;
459        assert_eq!(list.as_slice(), &[123, 99]);
460    }
461
462    #[test]
463    fn test_copy_from_slice() {
464        let mut list = FixedList::<u32, 5>::new();
465        let data = [9, 8, 7];
466        list.copy_from_slice(&data);
467        assert_eq!(list.len(), 3);
468        assert_eq!(list.as_slice(), &data);
469    }
470
471    #[test]
472    fn test_push_pop_collection_trait() {
473        let mut list = FixedList::<u8, 2>::new();
474        PushPopCollection::push(&mut list, 1).unwrap();
475        PushPopCollection::push(&mut list, 2).unwrap();
476        PushPopCollection::push(&mut list, 3).unwrap_err(); // should be ignored
477        assert_eq!(PushPopCollection::len(&list), 2);
478        assert_eq!(PushPopCollection::as_slice(&list), &[1, 2]);
479        assert_eq!(PushPopCollection::pop(&mut list), Some(2));
480        assert_eq!(PushPopCollection::pop(&mut list), Some(1));
481        assert_eq!(PushPopCollection::pop(&mut list), None);
482    }
483}