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}