spl_pod/list/
list_view.rs

1//! `ListView`, a compact, zero-copy array wrapper.
2
3use {
4    crate::{
5        bytemuck::{
6            pod_from_bytes, pod_from_bytes_mut, pod_slice_from_bytes, pod_slice_from_bytes_mut,
7        },
8        error::PodSliceError,
9        list::{list_view_mut::ListViewMut, list_view_read_only::ListViewReadOnly},
10        pod_length::PodLength,
11        primitives::PodU32,
12    },
13    bytemuck::Pod,
14    solana_program_error::ProgramError,
15    std::{
16        marker::PhantomData,
17        mem::{align_of, size_of},
18        ops::Range,
19    },
20};
21
22/// An API for interpreting a raw buffer (`&[u8]`) as a variable-length collection of Pod elements.
23///
24/// `ListView` provides a safe, zero-copy, `Vec`-like interface for a slice of
25/// `Pod` data that resides in an external, pre-allocated `&[u8]` buffer.
26/// It does not own the buffer itself, but acts as a view over it, which can be
27/// read-only (`ListViewReadOnly`) or mutable (`ListViewMut`).
28///
29/// This is useful in environments where allocations are restricted or expensive,
30/// such as Solana programs, allowing for efficient reads and manipulation of
31/// dynamic-length data structures.
32///
33/// ## Memory Layout
34///
35/// The structure assumes the underlying byte buffer is formatted as follows:
36/// 1.  **Length**: A length field of type `L` at the beginning of the buffer,
37///     indicating the number of currently active elements in the collection.
38///     Defaults to `PodU32`. The implementation uses padding to ensure that the
39///     data is correctly aligned for any `Pod` type.
40/// 2.  **Padding**: Optional padding bytes to ensure proper alignment of the data.
41/// 3.  **Data**: The remaining part of the buffer, which is treated as a slice
42///     of `T` elements. The capacity of the collection is the number of `T`
43///     elements that can fit into this data portion.
44pub struct ListView<T: Pod, L: PodLength = PodU32>(PhantomData<(T, L)>);
45
46struct Layout {
47    length_range: Range<usize>,
48    data_range: Range<usize>,
49}
50
51impl<T: Pod, L: PodLength> ListView<T, L> {
52    /// Calculate the total byte size for a `ListView` holding `num_items`.
53    /// This includes the length prefix, padding, and data.
54    pub fn size_of(num_items: usize) -> Result<usize, ProgramError> {
55        let header_padding = Self::header_padding()?;
56        size_of::<T>()
57            .checked_mul(num_items)
58            .and_then(|curr| curr.checked_add(size_of::<L>()))
59            .and_then(|curr| curr.checked_add(header_padding))
60            .ok_or_else(|| PodSliceError::CalculationFailure.into())
61    }
62
63    /// Unpack a read-only buffer into a `ListViewReadOnly`
64    pub fn unpack(buf: &[u8]) -> Result<ListViewReadOnly<T, L>, ProgramError> {
65        let layout = Self::calculate_layout(buf.len())?;
66
67        // Slice the buffer to get the length prefix and the data.
68        // The layout calculation provides the correct ranges, accounting for any
69        // padding between the length and the data.
70        //
71        // buf: [ L L L L | P P | D D D D D D D D ...]
72        //       <----->         <------------------>
73        //      len_bytes            data_bytes
74        let len_bytes = &buf[layout.length_range];
75        let data_bytes = &buf[layout.data_range];
76
77        let length = pod_from_bytes::<L>(len_bytes)?;
78        let data = pod_slice_from_bytes::<T>(data_bytes)?;
79        let capacity = data.len();
80
81        if (*length).into() > capacity {
82            return Err(PodSliceError::BufferTooSmall.into());
83        }
84
85        Ok(ListViewReadOnly {
86            length,
87            data,
88            capacity,
89        })
90    }
91
92    /// Unpack the mutable buffer into a mutable `ListViewMut`
93    pub fn unpack_mut(buf: &mut [u8]) -> Result<ListViewMut<T, L>, ProgramError> {
94        let view = Self::build_mut_view(buf)?;
95        if (*view.length).into() > view.capacity {
96            return Err(PodSliceError::BufferTooSmall.into());
97        }
98        Ok(view)
99    }
100
101    /// Initialize a buffer: sets `length = 0` and returns a mutable `ListViewMut`.
102    pub fn init(buf: &mut [u8]) -> Result<ListViewMut<T, L>, ProgramError> {
103        let view = Self::build_mut_view(buf)?;
104        *view.length = L::try_from(0)?;
105        Ok(view)
106    }
107
108    /// Internal helper to build a mutable view without validation or initialization.
109    #[inline]
110    fn build_mut_view(buf: &mut [u8]) -> Result<ListViewMut<T, L>, ProgramError> {
111        let layout = Self::calculate_layout(buf.len())?;
112
113        // Split the buffer to get the length prefix and the data.
114        // buf: [ L L L L | P P | D D D D D D D D ...]
115        //       <---- head ---> <--- tail --------->
116        let (header_bytes, data_bytes) = buf.split_at_mut(layout.data_range.start);
117        // header: [ L L L L | P P ]
118        //           <----->
119        //          len_bytes
120        let len_bytes = &mut header_bytes[layout.length_range];
121
122        // Cast the bytes to typed data
123        let length = pod_from_bytes_mut::<L>(len_bytes)?;
124        let data = pod_slice_from_bytes_mut::<T>(data_bytes)?;
125        let capacity = data.len();
126
127        Ok(ListViewMut {
128            length,
129            data,
130            capacity,
131        })
132    }
133
134    /// Calculate the byte ranges for the length and data sections of the buffer
135    #[inline]
136    fn calculate_layout(buf_len: usize) -> Result<Layout, ProgramError> {
137        let len_field_end = size_of::<L>();
138        let header_padding = Self::header_padding()?;
139        let data_start = len_field_end.saturating_add(header_padding);
140
141        if buf_len < data_start {
142            return Err(PodSliceError::BufferTooSmall.into());
143        }
144
145        Ok(Layout {
146            length_range: 0..len_field_end,
147            data_range: data_start..buf_len,
148        })
149    }
150
151    /// Calculate the padding required to align the data part of the buffer.
152    ///
153    /// The goal is to ensure that the data field `T` starts at a memory offset
154    /// that is a multiple of its alignment requirement.
155    #[inline]
156    fn header_padding() -> Result<usize, ProgramError> {
157        // Enforce that the length prefix type `L` itself does not have alignment requirements
158        if align_of::<L>() != 1 {
159            return Err(ProgramError::InvalidArgument);
160        }
161
162        let length_size = size_of::<L>();
163        let data_align = align_of::<T>();
164
165        // No padding is needed for alignments of 0 or 1
166        if data_align == 0 || data_align == 1 {
167            return Ok(0);
168        }
169
170        // Find how many bytes `length_size` extends past an alignment boundary
171        #[allow(clippy::arithmetic_side_effects)]
172        let remainder = length_size.wrapping_rem(data_align);
173
174        // If already aligned (remainder is 0), no padding is needed.
175        // Otherwise, calculate the distance to the next alignment boundary.
176        if remainder == 0 {
177            Ok(0)
178        } else {
179            Ok(data_align.wrapping_sub(remainder))
180        }
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use {
187        super::*,
188        crate::{
189            list::List,
190            primitives::{PodU128, PodU16, PodU32, PodU64},
191        },
192        bytemuck_derive::{Pod as DerivePod, Zeroable},
193    };
194
195    #[test]
196    fn test_size_of_no_padding() {
197        // Case 1: T has align 1, so no padding is ever needed.
198        // 10 items * 1 byte/item + 4 bytes for length = 14
199        assert_eq!(ListView::<u8, PodU32>::size_of(10).unwrap(), 14);
200
201        // Case 2: size_of<L> is a multiple of align_of<T>, so no padding needed.
202        // T = u32 (size 4, align 4), L = PodU32 (size 4). 4 % 4 == 0.
203        // 10 items * 4 bytes/item + 4 bytes for length = 44
204        assert_eq!(ListView::<u32>::size_of(10).unwrap(), 44);
205
206        // Case 3: 0 items. Size should just be size_of<L> + padding.
207        // Padding is 0 here.
208        // 0 items * 4 bytes/item + 4 bytes for length = 4
209        assert_eq!(ListView::<u32>::size_of(0).unwrap(), 4);
210    }
211
212    #[test]
213    fn test_size_of_with_padding() {
214        // Case 1: Padding is required.
215        // T = u64 (size 8, align 8), L = PodU32 (size 4).
216        // Padding required to align data to 8 bytes is 4. (4 + 4 = 8)
217        // (10 items * 8 bytes/item) + 4 bytes for length + 4 bytes for padding = 88
218        assert_eq!(ListView::<u64, PodU32>::size_of(10).unwrap(), 88);
219
220        #[repr(C, align(16))]
221        #[derive(DerivePod, Zeroable, Copy, Clone)]
222        struct Align16(u128);
223
224        // Case 2: Custom struct with high alignment.
225        // size 16, align 16
226        // L = PodU64 (size 8).
227        // Padding required to align data to 16 bytes is 8. (8 + 8 = 16)
228        // (10 items * 16 bytes/item) + 8 bytes for length + 8 bytes for padding = 176
229        assert_eq!(ListView::<Align16>::size_of(10).unwrap(), 176);
230
231        // Case 3: 0 items with padding.
232        // Size should be size_of<L> + padding.
233        // L = PodU32 (size 4), T = u64 (align 8). Padding is 4.
234        // Total size = 4 + 4 = 8
235        assert_eq!(ListView::<u64, PodU32>::size_of(0).unwrap(), 8);
236    }
237
238    #[test]
239    fn test_size_of_overflow() {
240        // Case 1: Multiplication overflows.
241        // `size_of::<u16>() * usize::MAX` will overflow.
242        let err = ListView::<u16, PodU32>::size_of(usize::MAX).unwrap_err();
243        assert_eq!(err, PodSliceError::CalculationFailure.into());
244
245        // Case 2: Multiplication does not overflow, but subsequent addition does.
246        // `size_of::<u8>() * usize::MAX` does not overflow, but adding `size_of<L>` will.
247        let err = ListView::<u8, PodU32>::size_of(usize::MAX).unwrap_err();
248        assert_eq!(err, PodSliceError::CalculationFailure.into());
249    }
250
251    #[test]
252    fn test_fails_with_non_aligned_length_type() {
253        // A custom `PodLength` type with an alignment of 4
254        #[repr(C, align(4))]
255        #[derive(Debug, Copy, Clone, Zeroable, DerivePod)]
256        struct TestPodU32(u32);
257
258        // Implement the traits for `PodLength`
259        impl From<TestPodU32> for usize {
260            fn from(val: TestPodU32) -> Self {
261                val.0 as usize
262            }
263        }
264        impl TryFrom<usize> for TestPodU32 {
265            type Error = PodSliceError;
266            fn try_from(val: usize) -> Result<Self, Self::Error> {
267                Ok(Self(u32::try_from(val)?))
268            }
269        }
270
271        let mut buf = [0u8; 100];
272
273        let err_size_of = ListView::<u8, TestPodU32>::size_of(10).unwrap_err();
274        assert_eq!(err_size_of, ProgramError::InvalidArgument);
275
276        let err_unpack = ListView::<u8, TestPodU32>::unpack(&buf).unwrap_err();
277        assert_eq!(err_unpack, ProgramError::InvalidArgument);
278
279        let err_init = ListView::<u8, TestPodU32>::init(&mut buf).unwrap_err();
280        assert_eq!(err_init, ProgramError::InvalidArgument);
281    }
282
283    #[test]
284    fn test_padding_calculation() {
285        // `u8` has an alignment of 1, so no padding is ever needed.
286        assert_eq!(ListView::<u8, PodU32>::header_padding().unwrap(), 0);
287
288        // Zero-Sized Types like `()` have size 0 and align 1, requiring no padding.
289        assert_eq!(ListView::<(), PodU64>::header_padding().unwrap(), 0);
290
291        // When length and data have the same alignment.
292        assert_eq!(ListView::<u16, PodU16>::header_padding().unwrap(), 0);
293        assert_eq!(ListView::<u32, PodU32>::header_padding().unwrap(), 0);
294        assert_eq!(ListView::<u64, PodU64>::header_padding().unwrap(), 0);
295
296        // When data alignment is smaller than or perfectly divides the length size.
297        assert_eq!(ListView::<u16, PodU64>::header_padding().unwrap(), 0); // 8 % 2 = 0
298        assert_eq!(ListView::<u32, PodU64>::header_padding().unwrap(), 0); // 8 % 4 = 0
299
300        // When padding IS needed.
301        assert_eq!(ListView::<u32, PodU16>::header_padding().unwrap(), 2); // size_of<PodU16> is 2. To align to 4, need 2 bytes.
302        assert_eq!(ListView::<u64, PodU16>::header_padding().unwrap(), 6); // size_of<PodU16> is 2. To align to 8, need 6 bytes.
303        assert_eq!(ListView::<u64, PodU32>::header_padding().unwrap(), 4); // size_of<PodU32> is 4. To align to 8, need 4 bytes.
304
305        // Test with custom, higher alignments.
306        #[repr(C, align(8))]
307        #[derive(DerivePod, Zeroable, Copy, Clone)]
308        struct Align8(u64);
309
310        // Test against different length types
311        assert_eq!(ListView::<Align8, PodU16>::header_padding().unwrap(), 6); // 2 + 6 = 8
312        assert_eq!(ListView::<Align8, PodU32>::header_padding().unwrap(), 4); // 4 + 4 = 8
313        assert_eq!(ListView::<Align8, PodU64>::header_padding().unwrap(), 0); // 8 is already aligned
314
315        #[repr(C, align(16))]
316        #[derive(DerivePod, Zeroable, Copy, Clone)]
317        struct Align16(u128);
318
319        assert_eq!(ListView::<Align16, PodU16>::header_padding().unwrap(), 14); // 2 + 14 = 16
320        assert_eq!(ListView::<Align16, PodU32>::header_padding().unwrap(), 12); // 4 + 12 = 16
321        assert_eq!(ListView::<Align16, PodU64>::header_padding().unwrap(), 8); // 8 + 8 = 16
322    }
323
324    #[test]
325    fn test_unpack_success_no_padding() {
326        // T = u32 (align 4), L = PodU32 (size 4, align 4). No padding needed.
327        let length: u32 = 2;
328        let capacity: usize = 3;
329        let item_size = size_of::<u32>();
330        let len_size = size_of::<PodU32>();
331        let buf_size = len_size + capacity * item_size;
332        let mut buf = vec![0u8; buf_size];
333
334        let pod_len: PodU32 = length.into();
335        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
336
337        let data_start = len_size;
338        let items = [100u32, 200u32];
339        let items_bytes = bytemuck::cast_slice(&items);
340        buf[data_start..(data_start + items_bytes.len())].copy_from_slice(items_bytes);
341
342        let view_ro = ListView::<u32, PodU32>::unpack(&buf).unwrap();
343        assert_eq!(view_ro.len(), length as usize);
344        assert_eq!(view_ro.capacity(), capacity);
345        assert_eq!(*view_ro, items[..]);
346
347        let view_mut = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap();
348        assert_eq!(view_mut.len(), length as usize);
349        assert_eq!(view_mut.capacity(), capacity);
350        assert_eq!(*view_mut, items[..]);
351    }
352
353    #[test]
354    fn test_unpack_success_with_padding() {
355        // T = u64 (align 8), L = PodU32 (size 4, align 4). Needs 4 bytes padding.
356        let padding = ListView::<u64, PodU32>::header_padding().unwrap();
357        assert_eq!(padding, 4);
358
359        let length: u32 = 2;
360        let capacity: usize = 2;
361        let item_size = size_of::<u64>();
362        let len_size = size_of::<PodU32>();
363        let buf_size = len_size + padding + capacity * item_size;
364        let mut buf = vec![0u8; buf_size];
365
366        let pod_len: PodU32 = length.into();
367        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
368
369        // Data starts after length and padding
370        let data_start = len_size + padding;
371        let items = [100u64, 200u64];
372        let items_bytes = bytemuck::cast_slice(&items);
373        buf[data_start..(data_start + items_bytes.len())].copy_from_slice(items_bytes);
374
375        let view_ro = ListView::<u64, PodU32>::unpack(&buf).unwrap();
376        assert_eq!(view_ro.len(), length as usize);
377        assert_eq!(view_ro.capacity(), capacity);
378        assert_eq!(*view_ro, items[..]);
379
380        let view_mut = ListView::<u64, PodU32>::unpack_mut(&mut buf).unwrap();
381        assert_eq!(view_mut.len(), length as usize);
382        assert_eq!(view_mut.capacity(), capacity);
383        assert_eq!(*view_mut, items[..]);
384    }
385
386    #[test]
387    fn test_unpack_success_zero_length() {
388        let capacity: usize = 5;
389        let item_size = size_of::<u32>();
390        let len_size = size_of::<PodU32>();
391        let buf_size = len_size + capacity * item_size;
392        let mut buf = vec![0u8; buf_size];
393
394        let pod_len: PodU32 = 0u32.into();
395        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
396
397        let view_ro = ListView::<u32, PodU32>::unpack(&buf).unwrap();
398        assert_eq!(view_ro.len(), 0);
399        assert_eq!(view_ro.capacity(), capacity);
400        assert!(view_ro.is_empty());
401        assert_eq!(&*view_ro, &[] as &[u32]);
402
403        let view_mut = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap();
404        assert_eq!(view_mut.len(), 0);
405        assert_eq!(view_mut.capacity(), capacity);
406        assert!(view_mut.is_empty());
407        assert_eq!(&*view_mut, &[] as &[u32]);
408    }
409
410    #[test]
411    fn test_unpack_success_full_capacity() {
412        let length: u64 = 3;
413        let capacity: usize = 3;
414        let item_size = size_of::<u64>();
415        let len_size = size_of::<PodU64>();
416        let buf_size = len_size + capacity * item_size;
417        let mut buf = vec![0u8; buf_size];
418
419        let pod_len: PodU64 = length.into();
420        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
421
422        let data_start = len_size;
423        let items = [1u64, 2u64, 3u64];
424        let items_bytes = bytemuck::cast_slice(&items);
425        buf[data_start..].copy_from_slice(items_bytes);
426
427        let view_ro = ListView::<u64>::unpack(&buf).unwrap();
428        assert_eq!(view_ro.len(), length as usize);
429        assert_eq!(view_ro.capacity(), capacity);
430        assert_eq!(*view_ro, items[..]);
431
432        let view_mut = ListView::<u64>::unpack_mut(&mut buf).unwrap();
433        assert_eq!(view_mut.len(), length as usize);
434        assert_eq!(view_mut.capacity(), capacity);
435        assert_eq!(*view_mut, items[..]);
436    }
437
438    #[test]
439    fn test_unpack_fail_buffer_too_small_for_header() {
440        // T = u64 (align 8), L = PodU32 (size 4). Header size is 8.
441        let header_size = ListView::<u64, PodU32>::size_of(0).unwrap();
442        assert_eq!(header_size, 8);
443
444        // Provide a buffer smaller than the required header
445        let mut buf = vec![0u8; header_size - 1]; // 7 bytes
446
447        let err = ListView::<u64, PodU32>::unpack(&buf).unwrap_err();
448        assert_eq!(err, PodSliceError::BufferTooSmall.into());
449
450        let err = ListView::<u64, PodU32>::unpack_mut(&mut buf).unwrap_err();
451        assert_eq!(err, PodSliceError::BufferTooSmall.into());
452    }
453
454    #[test]
455    fn test_unpack_fail_declared_length_exceeds_capacity() {
456        let declared_length: u32 = 4;
457        let capacity: usize = 3; // buffer can only hold 3
458        let item_size = size_of::<u32>();
459        let len_size = size_of::<PodU32>();
460        let buf_size = len_size + capacity * item_size;
461        let mut buf = vec![0u8; buf_size];
462
463        // Write a length that is bigger than capacity
464        let pod_len: PodU32 = declared_length.into();
465        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
466
467        let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
468        assert_eq!(err, PodSliceError::BufferTooSmall.into());
469
470        let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
471        assert_eq!(err, PodSliceError::BufferTooSmall.into());
472    }
473
474    #[test]
475    fn test_unpack_fail_data_part_not_multiple_of_item_size() {
476        let len_size = size_of::<PodU32>();
477
478        // data part is 5 bytes, not a multiple of item_size (4)
479        let buf_size = len_size + 5;
480        let mut buf = vec![0u8; buf_size];
481
482        // bytemuck::try_cast_slice returns an alignment error, which we map to InvalidArgument
483
484        let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
485        assert_eq!(err, ProgramError::InvalidArgument);
486
487        let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
488        assert_eq!(err, ProgramError::InvalidArgument);
489    }
490
491    #[test]
492    fn test_unpack_empty_buffer() {
493        let mut buf = [];
494        let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
495        assert_eq!(err, PodSliceError::BufferTooSmall.into());
496
497        let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
498        assert_eq!(err, PodSliceError::BufferTooSmall.into());
499    }
500
501    #[test]
502    fn test_init_success_no_padding() {
503        // T = u32 (align 4), L = PodU32 (size 4). No padding needed.
504        let capacity: usize = 5;
505        let len_size = size_of::<PodU32>();
506        let buf_size = ListView::<u32, PodU32>::size_of(capacity).unwrap();
507        let mut buf = vec![0xFFu8; buf_size]; // Pre-fill to ensure init zeroes it
508
509        let view = ListView::<u32, PodU32>::init(&mut buf).unwrap();
510
511        assert_eq!(view.len(), 0);
512        assert_eq!(view.capacity(), capacity);
513        assert!(view.is_empty());
514
515        // Check that the underlying buffer's length was actually zeroed
516        let length_bytes = &buf[0..len_size];
517        assert_eq!(length_bytes, &[0u8; 4]);
518    }
519
520    #[test]
521    fn test_init_success_with_padding() {
522        // T = u64 (align 8), L = PodU32 (size 4). Needs 4 bytes padding.
523        let capacity: usize = 3;
524        let len_size = size_of::<PodU32>();
525        let buf_size = ListView::<u64, PodU32>::size_of(capacity).unwrap();
526        let mut buf = vec![0xFFu8; buf_size]; // Pre-fill to ensure init zeroes it
527
528        let view = ListView::<u64, PodU32>::init(&mut buf).unwrap();
529
530        assert_eq!(view.len(), 0);
531        assert_eq!(view.capacity(), capacity);
532        assert!(view.is_empty());
533
534        // Check that the underlying buffer's length was actually zeroed
535        let length_bytes = &buf[0..len_size];
536        assert_eq!(length_bytes, &[0u8; 4]);
537        // The padding bytes may or may not be zeroed, we don't assert on them.
538    }
539
540    #[test]
541    fn test_init_success_zero_capacity() {
542        // Test initializing a buffer that can only hold the header.
543        // T = u64 (align 8), L = PodU32 (size 4). Header size is 8.
544        let buf_size = ListView::<u64, PodU32>::size_of(0).unwrap();
545        assert_eq!(buf_size, 8);
546        let mut buf = vec![0xFFu8; buf_size];
547
548        let view = ListView::<u64, PodU32>::init(&mut buf).unwrap();
549
550        assert_eq!(view.len(), 0);
551        assert_eq!(view.capacity(), 0);
552        assert!(view.is_empty());
553
554        // Check that the underlying buffer's length was actually zeroed
555        let len_size = size_of::<PodU32>();
556        let length_bytes = &buf[0..len_size];
557        assert_eq!(length_bytes, &[0u8; 4]);
558    }
559
560    #[test]
561    fn test_init_fail_buffer_too_small() {
562        // Header requires 4 bytes (size_of<PodU32>)
563        let mut buf = vec![0u8; 3];
564        let err = ListView::<u32, PodU32>::init(&mut buf).unwrap_err();
565        assert_eq!(err, PodSliceError::BufferTooSmall.into());
566
567        // With padding, header requires 8 bytes (4 for len, 4 for pad)
568        let mut buf_padded = vec![0u8; 7];
569        let err_padded = ListView::<u64, PodU32>::init(&mut buf_padded).unwrap_err();
570        assert_eq!(err_padded, PodSliceError::BufferTooSmall.into());
571    }
572
573    #[test]
574    fn test_init_success_default_length_type() {
575        // This test uses the default L=PodU32 length type by omitting it.
576        // T = u32 (align 4), L = PodU32 (size 4). No padding needed as 4 % 4 == 0.
577        let capacity = 5;
578        let len_size = size_of::<PodU32>(); // Default L is PodU32
579        let buf_size = ListView::<u32>::size_of(capacity).unwrap();
580        let mut buf = vec![0xFFu8; buf_size]; // Pre-fill to ensure init zeroes it
581
582        let view = ListView::<u32>::init(&mut buf).unwrap();
583
584        assert_eq!(view.len(), 0);
585        assert_eq!(view.capacity(), capacity);
586        assert!(view.is_empty());
587
588        // Check that the underlying buffer's length (a u32) was actually zeroed
589        let length_bytes = &buf[0..len_size];
590        assert_eq!(length_bytes, &[0u8; 4]);
591    }
592
593    macro_rules! test_list_view_for_length_type {
594        ($test_name:ident, $LengthType:ty) => {
595            #[test]
596            fn $test_name() {
597                type T = u64;
598
599                let padding = ListView::<T, $LengthType>::header_padding().unwrap();
600                let length_usize = 2usize;
601                let capacity = 3;
602
603                let item_size = size_of::<T>();
604                let len_size = size_of::<$LengthType>();
605                let buf_size = len_size + padding + capacity * item_size;
606                let mut buf = vec![0u8; buf_size];
607
608                // Write length
609                let pod_len = <$LengthType>::try_from(length_usize).unwrap();
610                buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
611
612                // Write data
613                let data_start = len_size + padding;
614                let items = [1000 as T, 2000 as T];
615                let items_bytes = bytemuck::cast_slice(&items);
616                buf[data_start..(data_start + items_bytes.len())].copy_from_slice(items_bytes);
617
618                // Test read-only view
619                let view_ro = ListView::<T, $LengthType>::unpack(&buf).unwrap();
620                assert_eq!(view_ro.len(), length_usize);
621                assert_eq!(view_ro.capacity(), capacity);
622                assert_eq!(*view_ro, items[..]);
623
624                // Test mutable view
625                let mut buf_mut = buf.clone();
626                let view_mut = ListView::<T, $LengthType>::unpack_mut(&mut buf_mut).unwrap();
627                assert_eq!(view_mut.len(), length_usize);
628                assert_eq!(view_mut.capacity(), capacity);
629                assert_eq!(*view_mut, items[..]);
630
631                // Test init
632                let mut init_buf = vec![0xFFu8; buf_size];
633                let init_view = ListView::<T, $LengthType>::init(&mut init_buf).unwrap();
634                assert_eq!(init_view.len(), 0);
635                assert_eq!(init_view.capacity(), capacity);
636                assert_eq!(<$LengthType>::try_from(0usize).unwrap(), *init_view.length);
637            }
638        };
639    }
640
641    test_list_view_for_length_type!(list_view_with_pod_u16, PodU16);
642    test_list_view_for_length_type!(list_view_with_pod_u32, PodU32);
643    test_list_view_for_length_type!(list_view_with_pod_u64, PodU64);
644    test_list_view_for_length_type!(list_view_with_pod_u128, PodU128);
645}