Skip to main content

satellite_collections/macros/
fixed_array.rs

1/// This macro declares a new array type for a specified type
2#[macro_export]
3macro_rules! declare_fixed_array {
4    ($Name:ident, $T:ty, $SIZE:expr) => {
5        #[derive(Clone, Copy, Debug, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)]
6        #[repr(C)]
7        pub struct $Name {
8            items: [$T; $SIZE],
9            count: u16, // Using u16; ensure N <= u16::MAX
10            _padding: [u8; 14],
11        }
12
13        const _: () = {
14            use core::mem::{align_of, size_of};
15
16            const _ALIGN: usize = align_of::<$Name>();
17            const _SIZE: usize = size_of::<$Name>();
18            const _ITEM_SIZE: usize = size_of::<$T>() * $SIZE;
19            const _EXPECTED_SIZE: usize = _ITEM_SIZE + 2 + 14;
20            const _: () = assert!(
21                _SIZE == _EXPECTED_SIZE,
22                "Size mismatch in FixedArray struct!"
23            );
24        };
25
26        impl $Name {
27            /// Creates a new, empty `FixedArray` without incurring large
28            /// stack allocations.
29            ///
30            /// Instead of constructing the backing array on the stack using
31            /// `core::array::from_fn` (which results in a >4 KiB stack frame
32            /// when `$T` is large and `$SIZE` is high), we rely on
33            /// `core::mem::zeroed()` to directly initialise the returned
34            /// value in the _caller-provided out-pointer_.
35            ///
36            /// This pattern avoids exceeding the 4096-byte stack limit of
37            /// Solana BPF programs while still guaranteeing a fully-zeroed,
38            /// POD-safe structure thanks to the `Pod + Zeroable` bounds on
39            /// both the wrapper and the contained type.
40            pub fn new() -> Self {
41                // Ensures the capacity check is still performed without
42                // forcing the compiler to materialise the whole array on the
43                // stack.
44                assert!(
45                    $SIZE <= u16::MAX as usize,
46                    "Capacity N exceeds u16 for count field"
47                );
48
49                // SAFETY: `$Name` and `$T` are `Pod + Zeroable`, therefore a
50                // byte-wise zeroed instance is a valid value. The returned
51                // object has `count = 0`, `items` fully zeroed, and the
52                // padding cleared.
53                unsafe { core::mem::zeroed() }
54            }
55
56            /// Creates a new `FixedArray` from a slice.
57            ///
58            /// This method is **only** compiled for non-BPF targets (unit tests, off-chain
59            /// code, etc.). On Solana/Arch BPF we provide a minimal stub to avoid large
60            /// stack frames that would otherwise blow past the 4 KiB limit even if the
61            /// function is never called.
62            #[cfg(not(target_os = "solana"))]
63            pub fn from_slice(input_slice: &[$T]) -> Self {
64                let mut fa = Self::new(); // Construct directly in the return slot (see new())
65                let num_to_copy = core::cmp::min(input_slice.len(), $SIZE);
66
67                for i in 0..num_to_copy {
68                    // Safe because we stay within bounds and `fa` is initialised.
69                    fa.add(input_slice[i].clone());
70                }
71                fa
72            }
73
74            /// Returns the number of active elements in the array.
75            pub fn len(&self) -> usize {
76                self.count as usize
77            }
78
79            /// Returns `true` if the array contains no active elements.
80            pub fn is_empty(&self) -> bool {
81                self.count == 0
82            }
83
84            /// Returns the total capacity of the array.
85            pub fn capacity(&self) -> usize {
86                $SIZE
87            }
88
89            /// Returns `true` if the array is at full capacity.
90            pub fn is_full(&self) -> bool {
91                (self.count as usize) == $SIZE
92            }
93
94            /// Returns a reference to the element at `index`, or `None` if out of bounds.
95            pub fn get(&self, index: usize) -> Option<&$T> {
96                if index < self.len() {
97                    Some(&self.items[index])
98                } else {
99                    None
100                }
101            }
102
103            /// Returns a mutable reference to the element at `index`, or `None` if out of bounds.
104            pub fn get_mut(&mut self, index: usize) -> Option<&mut $T> {
105                if index < self.len() {
106                    Some(&mut self.items[index])
107                } else {
108                    None
109                }
110            }
111
112            /// Adds an item to the end of the collection.
113            ///
114            /// Returns `Ok(usize)` with the index of the added item if successful.
115            /// Returns `Err(T)` containing the item back if the collection is full.
116            pub fn add(&mut self, item: $T) -> Option<usize> {
117                if self.is_full() {
118                    None
119                } else {
120                    let index = self.count as usize;
121                    self.items[index] = item;
122                    self.count += 1;
123                    Some(index)
124                }
125            }
126
127            /// Removes an item from the specified `index` and returns it.
128            ///
129            /// Elements after the removed item are shifted to fill the gap.
130            /// The slot at the end of the (now shorter) active list is reset to `T::default()`.
131            /// Returns `None` if `index` is out of bounds.
132            pub fn remove_at(&mut self, index: usize) -> Option<$T> {
133                if index >= self.len() {
134                    return None;
135                }
136
137                let removed_item = self.items[index].clone();
138
139                for i in index..(self.len() - 1) {
140                    self.items[i] = self.items[i + 1].clone();
141                }
142
143                self.count -= 1; // Decrement count first
144
145                // Reset the slot that is no longer in use at the new end of active items
146                if $SIZE > 0 {
147                    // Check N to prevent panic on N=0 (though items array implies N > 0)
148                    self.items[self.len()] = <$T>::default(); // self.len() is the new count
149                }
150
151                Some(removed_item)
152            }
153
154            /// Removes the first occurrence of `item_to_remove`.
155            ///
156            /// Returns `true` if an item was removed, `false` otherwise.
157            /// Requires `T: PartialEq`.
158            pub fn remove_item(&mut self, item_to_remove: &$T) -> bool {
159                let mut found_index: Option<usize> = None;
160                for i in 0..self.len() {
161                    if self.items[i] == *item_to_remove {
162                        found_index = Some(i);
163                        break;
164                    }
165                }
166
167                if let Some(index) = found_index {
168                    self.remove_at(index);
169                    true
170                } else {
171                    false
172                }
173            }
174
175            /// Returns an iterator over the active elements.
176            pub fn iter(&self) -> impl Iterator<Item = &$T> + '_ {
177                self.items.iter().take(self.len())
178            }
179
180            /// Returns a mutable iterator over the active elements.
181            pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut $T> + '_ {
182                let len = self.len();
183                self.items.iter_mut().take(len)
184            }
185
186            /// Clears the array, removing all active elements and resetting them to `T::default()`.
187            pub fn clear(&mut self) {
188                for i in 0..self.len() {
189                    self.items[i] = <$T>::default();
190                }
191                self.count = 0;
192            }
193
194            /// Returns a slice containing all active elements.
195            pub fn as_slice(&self) -> &[$T] {
196                &self.items[0..self.len()]
197            }
198
199            /// Returns a mutable slice containing all active elements.
200            pub fn as_mut_slice(&mut self) -> &mut [$T] {
201                let len = self.len();
202                &mut self.items[0..len]
203            }
204
205            /// Retains only the elements specified by the predicate.
206            ///
207            /// In other words, remove all elements `e` such that `f(&e)` returns `false`.
208            /// This method operates in place, visiting each element exactly once in the
209            /// original order, and preserves the order of the retained elements.
210            ///
211            /// Because this function requires reading from and writing to the same
212            /// locations, the `T: Clone` bound is used when elements are shifted.
213            pub fn retain<F>(&mut self, mut f: F)
214            where
215                F: FnMut(&$T) -> bool,
216            {
217                let original_len = self.len();
218                let mut write_idx = 0;
219                let mut read_idx = 0;
220
221                // Iterate through the active part of the array
222                while read_idx < original_len {
223                    // Decide whether to keep the element at read_idx
224                    if f(&self.items[read_idx]) {
225                        // If keeping it and it's not already in the correct place, move it.
226                        if read_idx != write_idx {
227                            self.items[write_idx] = self.items[read_idx].clone();
228                        }
229                        write_idx += 1;
230                    }
231                    read_idx += 1;
232                }
233
234                // Reset the slots for elements that were removed (from new_len to original_len)
235                for i in write_idx..original_len {
236                    self.items[i] = <$T>::default();
237                }
238
239                self.count = write_idx as u16;
240            }
241
242            pub fn as_vec(&self) -> Vec<$T> {
243                self.items
244                    .iter()
245                    .take(self.count as usize)
246                    .cloned()
247                    .collect()
248            }
249        }
250
251        impl Default for $Name {
252            fn default() -> Self {
253                Self::new()
254            }
255        }
256    };
257}
258
259#[cfg(test)]
260mod tests {
261
262    #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, bytemuck::Pod, bytemuck::Zeroable)]
263    #[repr(C)]
264    struct TestItem {
265        id: u32,
266        data: [u8; 4], // Example field
267    }
268
269    const TEST_CAPACITY: usize = 3;
270    const LARGE_CAPACITY: usize = 50; // For serde N > 32 test
271    const OVER_U16_MAX_CAPACITY: usize = (u16::MAX as usize) + 1;
272
273    declare_fixed_array!(FixedArrayTestItemNone, TestItem, 0);
274    declare_fixed_array!(FixedArrayTestItemSingle, TestItem, 1);
275    declare_fixed_array!(FixedArrayTestItemThree, TestItem, 3);
276    declare_fixed_array!(FixedArrayTestItemFive, TestItem, 5);
277    declare_fixed_array!(FixedArrayTestItemTest, TestItem, TEST_CAPACITY);
278    declare_fixed_array!(FixedArrayTestItemLarge, TestItem, LARGE_CAPACITY);
279    declare_fixed_array!(FixedArrayTestItemOversized, TestItem, OVER_U16_MAX_CAPACITY);
280
281    #[test]
282    fn test_new_empty_full_capacity() {
283        let fa = FixedArrayTestItemTest::new();
284        assert_eq!(fa.len(), 0);
285        assert!(fa.is_empty());
286        assert!(!fa.is_full());
287        assert_eq!(fa.capacity(), TEST_CAPACITY);
288    }
289
290    #[test]
291    fn test_add_and_get() {
292        let mut fa = FixedArrayTestItemTest::new();
293        let item1 = TestItem {
294            id: 1,
295            data: [1; 4],
296        };
297        let item2 = TestItem {
298            id: 2,
299            data: [2; 4],
300        };
301
302        assert_eq!(fa.add(item1.clone()), Some(0));
303        assert_eq!(fa.len(), 1);
304        assert!(!fa.is_empty());
305        assert_eq!(fa.get(0), Some(&item1));
306        assert_eq!(fa.get(1), None); // Out of bounds for active items
307
308        assert_eq!(fa.add(item2.clone()), Some(1));
309        assert_eq!(fa.len(), 2);
310        assert_eq!(fa.get(1), Some(&item2));
311    }
312
313    #[test]
314    fn test_add_to_full_array() {
315        let mut fa = FixedArrayTestItemSingle::new();
316        let item1 = TestItem {
317            id: 1,
318            data: [1; 4],
319        };
320        let item2 = TestItem {
321            id: 2,
322            data: [2; 4],
323        };
324
325        assert_eq!(fa.add(item1.clone()), Some(0));
326        assert!(fa.is_full());
327        assert_eq!(fa.len(), 1);
328
329        match fa.add(item2.clone()) {
330            None => {}
331            Some(_) => panic!("Should not be able to add to a full array."),
332        }
333        assert_eq!(fa.len(), 1); // Length should remain unchanged
334    }
335
336    #[test]
337    fn test_remove_at_and_shifting() {
338        let mut fa = FixedArrayTestItemTest::new();
339        let item1 = TestItem {
340            id: 1,
341            data: [1; 4],
342        };
343        let item2 = TestItem {
344            id: 2,
345            data: [2; 4],
346        };
347        let item3 = TestItem {
348            id: 3,
349            data: [3; 4],
350        };
351
352        fa.add(item1.clone()).unwrap();
353        fa.add(item2.clone()).unwrap();
354        fa.add(item3.clone()).unwrap();
355        assert!(fa.is_full());
356
357        // Remove middle item
358        assert_eq!(fa.remove_at(1), Some(item2.clone()));
359        assert_eq!(fa.len(), 2);
360        assert!(!fa.is_full());
361        assert_eq!(fa.get(0), Some(&item1));
362        assert_eq!(fa.get(1), Some(&item3)); // item3 should shift to index 1
363        assert_eq!(fa.items[2], TestItem::default()); // Last slot reset
364
365        // Remove first item
366        assert_eq!(fa.remove_at(0), Some(item1.clone()));
367        assert_eq!(fa.len(), 1);
368        assert_eq!(fa.get(0), Some(&item3));
369        assert_eq!(fa.items[1], TestItem::default()); // Slot reset
370
371        // Remove last remaining item
372        assert_eq!(fa.remove_at(0), Some(item3.clone()));
373        assert_eq!(fa.len(), 0);
374        assert!(fa.is_empty());
375        assert_eq!(fa.items[0], TestItem::default()); // Slot reset
376
377        // Try removing from empty or out of bounds
378        assert_eq!(fa.remove_at(0), None);
379    }
380
381    #[test]
382    fn test_remove_at_clears_slot() {
383        let mut fa = FixedArrayTestItemTest::new();
384        let item1 = TestItem {
385            id: 1,
386            data: [1; 4],
387        };
388        fa.add(item1.clone()).unwrap();
389
390        // Check internal state before remove
391        assert_eq!(fa.items[0], item1);
392        assert_eq!(fa.items[1], TestItem::default());
393
394        fa.remove_at(0).unwrap();
395        assert_eq!(fa.len(), 0);
396        assert_eq!(
397            fa.items[0],
398            TestItem::default(),
399            "Slot should be cleared to default"
400        );
401    }
402
403    #[test]
404    fn test_remove_item() {
405        let mut fa = FixedArrayTestItemTest::new();
406        let item1 = TestItem {
407            id: 1,
408            data: [1; 4],
409        };
410        let item2 = TestItem {
411            id: 2,
412            data: [2; 4],
413        };
414        let item3 = TestItem {
415            id: 3,
416            data: [3; 4],
417        }; // Not added initially
418
419        fa.add(item1.clone()).unwrap();
420        fa.add(item2.clone()).unwrap();
421
422        assert!(fa.remove_item(&item1));
423        assert_eq!(fa.len(), 1);
424        assert_eq!(fa.get(0), Some(&item2));
425        assert_eq!(fa.items[1], TestItem::default()); // Check slot cleared
426
427        assert!(!fa.remove_item(&item1)); // Already removed
428        assert!(!fa.remove_item(&item3)); // Not present
429        assert_eq!(fa.len(), 1);
430    }
431
432    #[test]
433    fn test_iter_and_iter_mut() {
434        let mut fa = FixedArrayTestItemTest::new();
435        let item1 = TestItem {
436            id: 1,
437            data: [1; 4],
438        };
439        let item2 = TestItem {
440            id: 2,
441            data: [2; 4],
442        };
443
444        fa.add(item1.clone()).unwrap();
445        fa.add(item2.clone()).unwrap();
446
447        let collected_items: Vec<&TestItem> = fa.iter().collect();
448        assert_eq!(collected_items, vec![&item1, &item2]);
449
450        for item_ref in fa.iter_mut() {
451            item_ref.id += 10;
452        }
453
454        assert_eq!(fa.get(0).unwrap().id, 11);
455        assert_eq!(fa.get(1).unwrap().id, 12);
456    }
457
458    #[test]
459    fn test_clear() {
460        let mut fa = FixedArrayTestItemTest::new();
461        fa.add(TestItem {
462            id: 1,
463            data: [1; 4],
464        })
465        .unwrap();
466        fa.add(TestItem {
467            id: 2,
468            data: [2; 4],
469        })
470        .unwrap();
471        assert_eq!(fa.len(), 2);
472
473        fa.clear();
474        assert_eq!(fa.len(), 0);
475        assert!(fa.is_empty());
476        // Check that underlying array slots are defaulted
477        assert_eq!(fa.items[0], TestItem::default());
478        assert_eq!(fa.items[1], TestItem::default());
479    }
480
481    #[test]
482    fn test_as_slice() {
483        let mut fa = FixedArrayTestItemTest::new();
484        let item1 = TestItem {
485            id: 1,
486            data: [1; 4],
487        };
488        let item2 = TestItem {
489            id: 2,
490            data: [2; 4],
491        };
492
493        fa.add(item1.clone()).unwrap();
494        fa.add(item2.clone()).unwrap();
495
496        let slice = fa.as_slice();
497        assert_eq!(slice.len(), 2);
498        assert_eq!(slice[0], item1);
499        assert_eq!(slice[1], item2);
500
501        fa.as_mut_slice()[0].id = 100;
502        assert_eq!(fa.get(0).unwrap().id, 100);
503    }
504
505    #[test]
506    fn test_default_impl() {
507        let fa = FixedArrayTestItemTest::default();
508        assert_eq!(fa.len(), 0);
509        assert!(fa.is_empty());
510        assert_eq!(fa.capacity(), TEST_CAPACITY);
511    }
512
513    #[test]
514    fn test_from_slice() {
515        let item1 = TestItem {
516            id: 1,
517            data: [1; 4],
518        };
519        let item2 = TestItem {
520            id: 2,
521            data: [2; 4],
522        };
523        let item3 = TestItem {
524            id: 3,
525            data: [3; 4],
526        };
527        let item4 = TestItem {
528            id: 4,
529            data: [4; 4],
530        };
531
532        // Test 1: Empty slice
533        let slice_empty: &[TestItem] = &[];
534        let fa_empty = FixedArrayTestItemTest::from_slice(slice_empty);
535        assert_eq!(fa_empty.len(), 0);
536        assert!(fa_empty.is_empty());
537
538        // Test 2: Slice smaller than capacity
539        let slice_small = &[item1.clone(), item2.clone()];
540        let fa_small = FixedArrayTestItemTest::from_slice(slice_small);
541        assert_eq!(fa_small.len(), 2);
542        assert_eq!(fa_small.get(0), Some(&item1));
543        assert_eq!(fa_small.get(1), Some(&item2));
544        assert_eq!(fa_small.get(2), None);
545
546        // Test 3: Slice equal to capacity
547        let slice_exact = &[item1.clone(), item2.clone(), item3.clone()];
548        let fa_exact = FixedArrayTestItemTest::from_slice(slice_exact);
549        assert_eq!(fa_exact.len(), TEST_CAPACITY);
550        assert!(fa_exact.is_full());
551        assert_eq!(fa_exact.get(0), Some(&item1));
552        assert_eq!(fa_exact.get(1), Some(&item2));
553        assert_eq!(fa_exact.get(2), Some(&item3));
554
555        // Test 4: Slice larger than capacity
556        let slice_large = &[item1.clone(), item2.clone(), item3.clone(), item4.clone()];
557        let fa_large = FixedArrayTestItemTest::from_slice(slice_large);
558        assert_eq!(fa_large.len(), TEST_CAPACITY);
559        assert!(fa_large.is_full());
560        assert_eq!(fa_large.get(0), Some(&item1));
561        assert_eq!(fa_large.get(1), Some(&item2));
562        assert_eq!(fa_large.get(2), Some(&item3));
563        assert_eq!(fa_large.get(3), None); // Element 4 should not be included
564
565        // Test 5: Zero capacity array with non-empty slice (should remain empty)
566        let fa_zero_cap = FixedArrayTestItemNone::from_slice(slice_small);
567        assert_eq!(fa_zero_cap.len(), 0);
568        assert!(fa_zero_cap.is_empty());
569        assert!(fa_zero_cap.is_full()); // A zero-capacity array is always full
570
571        // Test 6: Zero capacity array with empty slice
572        let fa_zero_cap_empty_slice = FixedArrayTestItemNone::from_slice(slice_empty);
573        assert_eq!(fa_zero_cap_empty_slice.len(), 0);
574        assert!(fa_zero_cap_empty_slice.is_empty());
575        assert!(fa_zero_cap_empty_slice.is_full());
576    }
577
578    #[test]
579    fn test_retain() {
580        let item1 = TestItem {
581            id: 1,
582            data: [1; 4],
583        };
584        let item2 = TestItem {
585            id: 2,
586            data: [2; 4],
587        };
588        let item3 = TestItem {
589            id: 3,
590            data: [3; 4],
591        };
592        let item4 = TestItem {
593            id: 4,
594            data: [4; 4],
595        };
596        let item5 = TestItem {
597            id: 5,
598            data: [5; 4],
599        };
600
601        // Test 1: Retain all
602        let mut fa1 =
603            FixedArrayTestItemFive::from_slice(&[item1.clone(), item2.clone(), item3.clone()]);
604        fa1.retain(|item| item.id > 0); // Keep all
605        assert_eq!(fa1.len(), 3);
606        assert_eq!(
607            fa1.as_slice(),
608            &[item1.clone(), item2.clone(), item3.clone()]
609        );
610
611        // Test 2: Retain none
612        let mut fa2 =
613            FixedArrayTestItemFive::from_slice(&[item1.clone(), item2.clone(), item3.clone()]);
614        fa2.retain(|item| item.id > 10); // Keep none
615        assert_eq!(fa2.len(), 0);
616        assert!(fa2.is_empty());
617        assert_eq!(fa2.items[0], TestItem::default()); // Check slots are cleared
618        assert_eq!(fa2.items[1], TestItem::default());
619        assert_eq!(fa2.items[2], TestItem::default());
620
621        // Test 3: Retain some (even numbers)
622        let mut fa3 = FixedArrayTestItemFive::from_slice(&[
623            item1.clone(),
624            item2.clone(),
625            item3.clone(),
626            item4.clone(),
627            item5.clone(),
628        ]);
629        fa3.retain(|item| item.id % 2 == 0); // Keep 2, 4
630        assert_eq!(fa3.len(), 2);
631        assert_eq!(fa3.as_slice(), &[item2.clone(), item4.clone()]);
632        assert_eq!(fa3.items[2], TestItem::default()); // Check slots are cleared
633        assert_eq!(fa3.items[3], TestItem::default());
634        assert_eq!(fa3.items[4], TestItem::default());
635
636        // Test 4: Retain from an empty array
637        let mut fa4 = FixedArrayTestItemFive::new();
638        fa4.retain(|item| item.id > 0);
639        assert_eq!(fa4.len(), 0);
640        assert!(fa4.is_empty());
641
642        // Test 5: Retain that removes from the beginning
643        let mut fa5 =
644            FixedArrayTestItemThree::from_slice(&[item1.clone(), item2.clone(), item3.clone()]);
645        fa5.retain(|item| item.id > 1); // Keep 2, 3
646        assert_eq!(fa5.len(), 2);
647        assert_eq!(fa5.as_slice(), &[item2.clone(), item3.clone()]);
648        assert_eq!(fa5.items[2], TestItem::default());
649
650        // Test 6: Retain that removes from the end
651        let mut fa6 =
652            FixedArrayTestItemThree::from_slice(&[item1.clone(), item2.clone(), item3.clone()]);
653        fa6.retain(|item| item.id < 3); // Keep 1, 2
654        assert_eq!(fa6.len(), 2);
655        assert_eq!(fa6.as_slice(), &[item1.clone(), item2.clone()]);
656        assert_eq!(fa6.items[2], TestItem::default());
657
658        // Test 7: Retain that removes from the middle
659        let mut fa7 =
660            FixedArrayTestItemThree::from_slice(&[item1.clone(), item2.clone(), item3.clone()]);
661        fa7.retain(|item| item.id == 1 || item.id == 3); // Keep 1, 3
662        assert_eq!(fa7.len(), 2);
663        assert_eq!(fa7.as_slice(), &[item1.clone(), item3.clone()]);
664        assert_eq!(fa7.items[2], TestItem::default());
665
666        // Test 8: Retain on a full array resulting in a partially full array
667        let mut fa8 =
668            FixedArrayTestItemThree::from_slice(&[item1.clone(), item2.clone(), item3.clone()]);
669        assert!(fa8.is_full());
670        fa8.retain(|item| item.id < 2); // Keep 1
671        assert_eq!(fa8.len(), 1);
672        assert_eq!(fa8.as_slice(), &[item1.clone()]);
673        assert!(!fa8.is_full());
674        assert_eq!(fa8.items[1], TestItem::default());
675        assert_eq!(fa8.items[2], TestItem::default());
676    }
677
678    // #[test]
679    // fn test_borsh_serialization_large_array() {
680    //     let mut fa = FixedArrayTestItemLarge::new();
681    //     fa.add(TestItem {
682    //         id: 10,
683    //         data: [1; 4],
684    //     })
685    //     .unwrap();
686    //     fa.add(TestItem {
687    //         id: 20,
688    //         data: [2; 4],
689    //     })
690    //     .unwrap();
691
692    //     let encoded = borsh::to_vec(&fa).expect("Borsh serialization failed");
693    //     let decoded: FixedArray<TestItem, LARGE_CAPACITY> =
694    //         borsh::from_slice(&encoded).expect("Borsh deserialization failed");
695
696    //     assert_eq!(
697    //         fa, decoded,
698    //         "Original and Borsh deserialized FixedArray differ"
699    //     );
700    //     assert_eq!(decoded.len(), 2);
701    //     assert_eq!(decoded.get(1).unwrap().id, 20);
702    // }
703
704    #[test]
705    #[should_panic(expected = r#"Capacity N exceeds u16 for count field"#)]
706    fn test_new_panics_on_too_large_n() {
707        // This test will only work if u16::MAX is small enough to actually allocate
708        // For most systems, usize will be larger, so this specific test might not reflect
709        // a practical memory limit, but tests the assertion.
710        // const HUGE_CAPACITY: usize = (u16::MAX as usize) + 1;
711        // FixedArray::<TestItem, HUGE_CAPACITY>::new();
712        // Re-evaluating how to test this panic safely.
713        // The assertion is `N <= u16::MAX as usize`.
714        // If usize is 64-bit, this is fine. The issue would be if N was truly huge.
715        // For now, the assertion is a safeguard for `count` field type.
716        // Let's assume the typical N will be much smaller.
717        // For a direct test of the panic, we'd need a const N > u16::MAX.
718        // This can be simulated if we reduce u16::MAX for the test or use a very large N.
719        // Since N is a const generic, this is tricky to test dynamically for the panic.
720        // The assertion itself is the primary check.
721        // For the purpose of this unit test, we'll assume N is within reasonable bounds.
722        // If we want to force the panic for testing:
723        struct MockU16MaxTest; // Create a dummy type
724                               // If we could define `FixedArray<MockU16MaxTest, 65537>` here, it would test.
725                               // However, this needs to be a compile-time constant.
726
727        // This kind of test is hard to do for const generics within a single test function
728        // without conditional compilation or specific build setups.
729        // The assertion is clear enough: `assert!(N <= u16::MAX as usize, ...)`
730        // Let's assume the assertion itself serves its purpose.
731        // If a user tries `FixedArray<T, 70000>`, it will panic at `new()`.
732        // For now, we trust the assertion and skip a direct panic test for this.
733        // To actually test it, one would need to instantiate with N > 65535.
734        // E.g. `FixedArray::<u8, 70000>::new();` would trigger it.
735        // We can add a specific test case if required, but it will only compile if N can be that large.
736
737        // Forcing a panic for the test with a specific large N value:
738        FixedArrayTestItemOversized::new(); // This line should panic
739    }
740}