Skip to main content

spl_list_view/
list_view.rs

1//! `ListView`, a compact, zero-copy array wrapper.
2
3use {
4    crate::{list_view_mut::ListViewMut, list_view_read_only::ListViewReadOnly},
5    bytemuck::Pod,
6    core::{
7        marker::PhantomData,
8        mem::{align_of, size_of},
9        ops::Range,
10    },
11    solana_program_error::ProgramError,
12    spl_pod::{
13        bytemuck::{
14            pod_from_bytes, pod_from_bytes_mut, pod_slice_from_bytes, pod_slice_from_bytes_mut,
15        },
16        error::PodSliceError,
17        pod_length::PodLength,
18        primitives::PodU32,
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::List,
189        bytemuck_derive::{Pod as DerivePod, Zeroable},
190        spl_pod::primitives::{PodU128, PodU16, PodU32, PodU64},
191    };
192
193    #[test]
194    fn test_size_of_no_padding() {
195        // Case 1: T has align 1, so no padding is ever needed.
196        // 10 items * 1 byte/item + 4 bytes for length = 14
197        assert_eq!(ListView::<u8, PodU32>::size_of(10).unwrap(), 14);
198
199        // Case 2: size_of<L> is a multiple of align_of<T>, so no padding needed.
200        // T = u32 (size 4, align 4), L = PodU32 (size 4). 4 % 4 == 0.
201        // 10 items * 4 bytes/item + 4 bytes for length = 44
202        assert_eq!(ListView::<u32>::size_of(10).unwrap(), 44);
203
204        // Case 3: 0 items. Size should just be size_of<L> + padding.
205        // Padding is 0 here.
206        // 0 items * 4 bytes/item + 4 bytes for length = 4
207        assert_eq!(ListView::<u32>::size_of(0).unwrap(), 4);
208    }
209
210    #[test]
211    fn test_size_of_with_padding() {
212        // Case 1: Padding is required.
213        // T = u64 (size 8, align 8), L = PodU32 (size 4).
214        // Padding required to align data to 8 bytes is 4. (4 + 4 = 8)
215        // (10 items * 8 bytes/item) + 4 bytes for length + 4 bytes for padding = 88
216        assert_eq!(ListView::<u64, PodU32>::size_of(10).unwrap(), 88);
217
218        #[repr(C, align(16))]
219        #[derive(DerivePod, Zeroable, Copy, Clone)]
220        struct Align16(u128);
221
222        // Case 2: Custom struct with high alignment.
223        // size 16, align 16
224        // L = PodU64 (size 8).
225        // Padding required to align data to 16 bytes is 8. (8 + 8 = 16)
226        // (10 items * 16 bytes/item) + 8 bytes for length + 8 bytes for padding = 176
227        assert_eq!(ListView::<Align16>::size_of(10).unwrap(), 176);
228
229        // Case 3: 0 items with padding.
230        // Size should be size_of<L> + padding.
231        // L = PodU32 (size 4), T = u64 (align 8). Padding is 4.
232        // Total size = 4 + 4 = 8
233        assert_eq!(ListView::<u64, PodU32>::size_of(0).unwrap(), 8);
234    }
235
236    #[test]
237    fn test_size_of_overflow() {
238        // Case 1: Multiplication overflows.
239        // `size_of::<u16>() * usize::MAX` will overflow.
240        let err = ListView::<u16, PodU32>::size_of(usize::MAX).unwrap_err();
241        assert_eq!(err, PodSliceError::CalculationFailure.into());
242
243        // Case 2: Multiplication does not overflow, but subsequent addition does.
244        // `size_of::<u8>() * usize::MAX` does not overflow, but adding `size_of<L>` will.
245        let err = ListView::<u8, PodU32>::size_of(usize::MAX).unwrap_err();
246        assert_eq!(err, PodSliceError::CalculationFailure.into());
247    }
248
249    #[test]
250    fn test_fails_with_non_aligned_length_type() {
251        // A custom `PodLength` type with an alignment of 4
252        #[repr(C, align(4))]
253        #[derive(Debug, Copy, Clone, Zeroable, DerivePod)]
254        struct TestPodU32(u32);
255
256        // Implement the traits for `PodLength`
257        impl From<TestPodU32> for usize {
258            fn from(val: TestPodU32) -> Self {
259                val.0 as usize
260            }
261        }
262        impl TryFrom<usize> for TestPodU32 {
263            type Error = PodSliceError;
264            fn try_from(val: usize) -> Result<Self, Self::Error> {
265                Ok(Self(u32::try_from(val)?))
266            }
267        }
268
269        let mut buf = [0u8; 100];
270
271        let err_size_of = ListView::<u8, TestPodU32>::size_of(10).unwrap_err();
272        assert_eq!(err_size_of, ProgramError::InvalidArgument);
273
274        let err_unpack = ListView::<u8, TestPodU32>::unpack(&buf).unwrap_err();
275        assert_eq!(err_unpack, ProgramError::InvalidArgument);
276
277        let err_init = ListView::<u8, TestPodU32>::init(&mut buf).unwrap_err();
278        assert_eq!(err_init, ProgramError::InvalidArgument);
279    }
280
281    #[test]
282    fn test_padding_calculation() {
283        // `u8` has an alignment of 1, so no padding is ever needed.
284        assert_eq!(ListView::<u8, PodU32>::header_padding().unwrap(), 0);
285
286        // Zero-Sized Types like `()` have size 0 and align 1, requiring no padding.
287        assert_eq!(ListView::<(), PodU64>::header_padding().unwrap(), 0);
288
289        // When length and data have the same alignment.
290        assert_eq!(ListView::<u16, PodU16>::header_padding().unwrap(), 0);
291        assert_eq!(ListView::<u32, PodU32>::header_padding().unwrap(), 0);
292        assert_eq!(ListView::<u64, PodU64>::header_padding().unwrap(), 0);
293
294        // When data alignment is smaller than or perfectly divides the length size.
295        assert_eq!(ListView::<u16, PodU64>::header_padding().unwrap(), 0); // 8 % 2 = 0
296        assert_eq!(ListView::<u32, PodU64>::header_padding().unwrap(), 0); // 8 % 4 = 0
297
298        // When padding IS needed.
299        assert_eq!(ListView::<u32, PodU16>::header_padding().unwrap(), 2); // size_of<PodU16> is 2. To align to 4, need 2 bytes.
300        assert_eq!(ListView::<u64, PodU16>::header_padding().unwrap(), 6); // size_of<PodU16> is 2. To align to 8, need 6 bytes.
301        assert_eq!(ListView::<u64, PodU32>::header_padding().unwrap(), 4); // size_of<PodU32> is 4. To align to 8, need 4 bytes.
302
303        // Test with custom, higher alignments.
304        #[repr(C, align(8))]
305        #[derive(DerivePod, Zeroable, Copy, Clone)]
306        struct Align8(u64);
307
308        // Test against different length types
309        assert_eq!(ListView::<Align8, PodU16>::header_padding().unwrap(), 6); // 2 + 6 = 8
310        assert_eq!(ListView::<Align8, PodU32>::header_padding().unwrap(), 4); // 4 + 4 = 8
311        assert_eq!(ListView::<Align8, PodU64>::header_padding().unwrap(), 0); // 8 is already aligned
312
313        #[repr(C, align(16))]
314        #[derive(DerivePod, Zeroable, Copy, Clone)]
315        struct Align16(u128);
316
317        assert_eq!(ListView::<Align16, PodU16>::header_padding().unwrap(), 14); // 2 + 14 = 16
318        assert_eq!(ListView::<Align16, PodU32>::header_padding().unwrap(), 12); // 4 + 12 = 16
319        assert_eq!(ListView::<Align16, PodU64>::header_padding().unwrap(), 8); // 8 + 8 = 16
320    }
321
322    #[test]
323    fn test_unpack_success_no_padding() {
324        // T = u32 (align 4), L = PodU32 (size 4, align 4). No padding needed.
325        let length: u32 = 2;
326        let capacity: usize = 3;
327        let item_size = size_of::<u32>();
328        let len_size = size_of::<PodU32>();
329        let buf_size = len_size + capacity * item_size;
330        let mut buf = vec![0u8; buf_size];
331
332        let pod_len: PodU32 = length.into();
333        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
334
335        let data_start = len_size;
336        let items = [100u32, 200u32];
337        let items_bytes = bytemuck::cast_slice(&items);
338        buf[data_start..(data_start + items_bytes.len())].copy_from_slice(items_bytes);
339
340        let view_ro = ListView::<u32, PodU32>::unpack(&buf).unwrap();
341        assert_eq!(view_ro.len(), length as usize);
342        assert_eq!(view_ro.capacity(), capacity);
343        assert_eq!(*view_ro, items[..]);
344
345        let view_mut = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap();
346        assert_eq!(view_mut.len(), length as usize);
347        assert_eq!(view_mut.capacity(), capacity);
348        assert_eq!(*view_mut, items[..]);
349    }
350
351    #[test]
352    fn test_unpack_success_with_padding() {
353        // T = u64 (align 8), L = PodU32 (size 4, align 4). Needs 4 bytes padding.
354        let padding = ListView::<u64, PodU32>::header_padding().unwrap();
355        assert_eq!(padding, 4);
356
357        let length: u32 = 2;
358        let capacity: usize = 2;
359        let item_size = size_of::<u64>();
360        let len_size = size_of::<PodU32>();
361        let buf_size = len_size + padding + capacity * item_size;
362        let mut buf = vec![0u8; buf_size];
363
364        let pod_len: PodU32 = length.into();
365        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
366
367        // Data starts after length and padding
368        let data_start = len_size + padding;
369        let items = [100u64, 200u64];
370        let items_bytes = bytemuck::cast_slice(&items);
371        buf[data_start..(data_start + items_bytes.len())].copy_from_slice(items_bytes);
372
373        let view_ro = ListView::<u64, PodU32>::unpack(&buf).unwrap();
374        assert_eq!(view_ro.len(), length as usize);
375        assert_eq!(view_ro.capacity(), capacity);
376        assert_eq!(*view_ro, items[..]);
377
378        let view_mut = ListView::<u64, PodU32>::unpack_mut(&mut buf).unwrap();
379        assert_eq!(view_mut.len(), length as usize);
380        assert_eq!(view_mut.capacity(), capacity);
381        assert_eq!(*view_mut, items[..]);
382    }
383
384    #[test]
385    fn test_unpack_success_zero_length() {
386        let capacity: usize = 5;
387        let item_size = size_of::<u32>();
388        let len_size = size_of::<PodU32>();
389        let buf_size = len_size + capacity * item_size;
390        let mut buf = vec![0u8; buf_size];
391
392        let pod_len: PodU32 = 0u32.into();
393        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
394
395        let view_ro = ListView::<u32, PodU32>::unpack(&buf).unwrap();
396        assert_eq!(view_ro.len(), 0);
397        assert_eq!(view_ro.capacity(), capacity);
398        assert!(view_ro.is_empty());
399        assert_eq!(&*view_ro, &[] as &[u32]);
400
401        let view_mut = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap();
402        assert_eq!(view_mut.len(), 0);
403        assert_eq!(view_mut.capacity(), capacity);
404        assert!(view_mut.is_empty());
405        assert_eq!(&*view_mut, &[] as &[u32]);
406    }
407
408    #[test]
409    fn test_unpack_success_full_capacity() {
410        let length: u64 = 3;
411        let capacity: usize = 3;
412        let item_size = size_of::<u64>();
413        let len_size = size_of::<PodU64>();
414        let buf_size = len_size + capacity * item_size;
415        let mut buf = vec![0u8; buf_size];
416
417        let pod_len: PodU64 = length.into();
418        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
419
420        let data_start = len_size;
421        let items = [1u64, 2u64, 3u64];
422        let items_bytes = bytemuck::cast_slice(&items);
423        buf[data_start..].copy_from_slice(items_bytes);
424
425        let view_ro = ListView::<u64>::unpack(&buf).unwrap();
426        assert_eq!(view_ro.len(), length as usize);
427        assert_eq!(view_ro.capacity(), capacity);
428        assert_eq!(*view_ro, items[..]);
429
430        let view_mut = ListView::<u64>::unpack_mut(&mut buf).unwrap();
431        assert_eq!(view_mut.len(), length as usize);
432        assert_eq!(view_mut.capacity(), capacity);
433        assert_eq!(*view_mut, items[..]);
434    }
435
436    #[test]
437    fn test_unpack_fail_buffer_too_small_for_header() {
438        // T = u64 (align 8), L = PodU32 (size 4). Header size is 8.
439        let header_size = ListView::<u64, PodU32>::size_of(0).unwrap();
440        assert_eq!(header_size, 8);
441
442        // Provide a buffer smaller than the required header
443        let mut buf = vec![0u8; header_size - 1]; // 7 bytes
444
445        let err = ListView::<u64, PodU32>::unpack(&buf).unwrap_err();
446        assert_eq!(err, PodSliceError::BufferTooSmall.into());
447
448        let err = ListView::<u64, PodU32>::unpack_mut(&mut buf).unwrap_err();
449        assert_eq!(err, PodSliceError::BufferTooSmall.into());
450    }
451
452    #[test]
453    fn test_unpack_fail_declared_length_exceeds_capacity() {
454        let declared_length: u32 = 4;
455        let capacity: usize = 3; // buffer can only hold 3
456        let item_size = size_of::<u32>();
457        let len_size = size_of::<PodU32>();
458        let buf_size = len_size + capacity * item_size;
459        let mut buf = vec![0u8; buf_size];
460
461        // Write a length that is bigger than capacity
462        let pod_len: PodU32 = declared_length.into();
463        buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
464
465        let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
466        assert_eq!(err, PodSliceError::BufferTooSmall.into());
467
468        let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
469        assert_eq!(err, PodSliceError::BufferTooSmall.into());
470    }
471
472    #[test]
473    fn test_unpack_fail_data_part_not_multiple_of_item_size() {
474        let len_size = size_of::<PodU32>();
475
476        // data part is 5 bytes, not a multiple of item_size (4)
477        let buf_size = len_size + 5;
478        let mut buf = vec![0u8; buf_size];
479
480        // bytemuck::try_cast_slice returns an alignment error, which we map to InvalidArgument
481
482        let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
483        assert_eq!(err, ProgramError::InvalidArgument);
484
485        let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
486        assert_eq!(err, ProgramError::InvalidArgument);
487    }
488
489    #[test]
490    fn test_unpack_empty_buffer() {
491        let mut buf = [];
492        let err = ListView::<u32, PodU32>::unpack(&buf).unwrap_err();
493        assert_eq!(err, PodSliceError::BufferTooSmall.into());
494
495        let err = ListView::<u32, PodU32>::unpack_mut(&mut buf).unwrap_err();
496        assert_eq!(err, PodSliceError::BufferTooSmall.into());
497    }
498
499    #[test]
500    fn test_init_success_no_padding() {
501        // T = u32 (align 4), L = PodU32 (size 4). No padding needed.
502        let capacity: usize = 5;
503        let len_size = size_of::<PodU32>();
504        let buf_size = ListView::<u32, PodU32>::size_of(capacity).unwrap();
505        let mut buf = vec![0xFFu8; buf_size]; // Pre-fill to ensure init zeroes it
506
507        let view = ListView::<u32, PodU32>::init(&mut buf).unwrap();
508
509        assert_eq!(view.len(), 0);
510        assert_eq!(view.capacity(), capacity);
511        assert!(view.is_empty());
512
513        // Check that the underlying buffer's length was actually zeroed
514        let length_bytes = &buf[0..len_size];
515        assert_eq!(length_bytes, &[0u8; 4]);
516    }
517
518    #[test]
519    fn test_init_success_with_padding() {
520        // T = u64 (align 8), L = PodU32 (size 4). Needs 4 bytes padding.
521        let capacity: usize = 3;
522        let len_size = size_of::<PodU32>();
523        let buf_size = ListView::<u64, PodU32>::size_of(capacity).unwrap();
524        let mut buf = vec![0xFFu8; buf_size]; // Pre-fill to ensure init zeroes it
525
526        let view = ListView::<u64, PodU32>::init(&mut buf).unwrap();
527
528        assert_eq!(view.len(), 0);
529        assert_eq!(view.capacity(), capacity);
530        assert!(view.is_empty());
531
532        // Check that the underlying buffer's length was actually zeroed
533        let length_bytes = &buf[0..len_size];
534        assert_eq!(length_bytes, &[0u8; 4]);
535        // The padding bytes may or may not be zeroed, we don't assert on them.
536    }
537
538    #[test]
539    fn test_init_success_zero_capacity() {
540        // Test initializing a buffer that can only hold the header.
541        // T = u64 (align 8), L = PodU32 (size 4). Header size is 8.
542        let buf_size = ListView::<u64, PodU32>::size_of(0).unwrap();
543        assert_eq!(buf_size, 8);
544        let mut buf = vec![0xFFu8; buf_size];
545
546        let view = ListView::<u64, PodU32>::init(&mut buf).unwrap();
547
548        assert_eq!(view.len(), 0);
549        assert_eq!(view.capacity(), 0);
550        assert!(view.is_empty());
551
552        // Check that the underlying buffer's length was actually zeroed
553        let len_size = size_of::<PodU32>();
554        let length_bytes = &buf[0..len_size];
555        assert_eq!(length_bytes, &[0u8; 4]);
556    }
557
558    #[test]
559    fn test_init_fail_buffer_too_small() {
560        // Header requires 4 bytes (size_of<PodU32>)
561        let mut buf = vec![0u8; 3];
562        let err = ListView::<u32, PodU32>::init(&mut buf).unwrap_err();
563        assert_eq!(err, PodSliceError::BufferTooSmall.into());
564
565        // With padding, header requires 8 bytes (4 for len, 4 for pad)
566        let mut buf_padded = vec![0u8; 7];
567        let err_padded = ListView::<u64, PodU32>::init(&mut buf_padded).unwrap_err();
568        assert_eq!(err_padded, PodSliceError::BufferTooSmall.into());
569    }
570
571    #[test]
572    fn test_init_success_default_length_type() {
573        // This test uses the default L=PodU32 length type by omitting it.
574        // T = u32 (align 4), L = PodU32 (size 4). No padding needed as 4 % 4 == 0.
575        let capacity = 5;
576        let len_size = size_of::<PodU32>(); // Default L is PodU32
577        let buf_size = ListView::<u32>::size_of(capacity).unwrap();
578        let mut buf = vec![0xFFu8; buf_size]; // Pre-fill to ensure init zeroes it
579
580        let view = ListView::<u32>::init(&mut buf).unwrap();
581
582        assert_eq!(view.len(), 0);
583        assert_eq!(view.capacity(), capacity);
584        assert!(view.is_empty());
585
586        // Check that the underlying buffer's length (a u32) was actually zeroed
587        let length_bytes = &buf[0..len_size];
588        assert_eq!(length_bytes, &[0u8; 4]);
589    }
590
591    macro_rules! test_list_view_for_length_type {
592        ($test_name:ident, $LengthType:ty) => {
593            #[test]
594            fn $test_name() {
595                type T = u64;
596
597                let padding = ListView::<T, $LengthType>::header_padding().unwrap();
598                let length_usize = 2usize;
599                let capacity = 3;
600
601                let item_size = size_of::<T>();
602                let len_size = size_of::<$LengthType>();
603                let buf_size = len_size + padding + capacity * item_size;
604                let mut buf = vec![0u8; buf_size];
605
606                // Write length
607                let pod_len = <$LengthType>::try_from(length_usize).unwrap();
608                buf[0..len_size].copy_from_slice(bytemuck::bytes_of(&pod_len));
609
610                // Write data
611                let data_start = len_size + padding;
612                let items = [1000 as T, 2000 as T];
613                let items_bytes = bytemuck::cast_slice(&items);
614                buf[data_start..(data_start + items_bytes.len())].copy_from_slice(items_bytes);
615
616                // Test read-only view
617                let view_ro = ListView::<T, $LengthType>::unpack(&buf).unwrap();
618                assert_eq!(view_ro.len(), length_usize);
619                assert_eq!(view_ro.capacity(), capacity);
620                assert_eq!(*view_ro, items[..]);
621
622                // Test mutable view
623                let mut buf_mut = buf.clone();
624                let view_mut = ListView::<T, $LengthType>::unpack_mut(&mut buf_mut).unwrap();
625                assert_eq!(view_mut.len(), length_usize);
626                assert_eq!(view_mut.capacity(), capacity);
627                assert_eq!(*view_mut, items[..]);
628
629                // Test init
630                let mut init_buf = vec![0xFFu8; buf_size];
631                let init_view = ListView::<T, $LengthType>::init(&mut init_buf).unwrap();
632                assert_eq!(init_view.len(), 0);
633                assert_eq!(init_view.capacity(), capacity);
634                assert_eq!(<$LengthType>::try_from(0usize).unwrap(), *init_view.length);
635            }
636        };
637    }
638
639    test_list_view_for_length_type!(list_view_with_pod_u16, PodU16);
640    test_list_view_for_length_type!(list_view_with_pod_u32, PodU32);
641    test_list_view_for_length_type!(list_view_with_pod_u64, PodU64);
642    test_list_view_for_length_type!(list_view_with_pod_u128, PodU128);
643}