spl_pod/list/
list_view_read_only.rs

1//! `ListViewReadOnly`, a read-only, compact, zero-copy array wrapper.
2
3use {
4    crate::{list::list_trait::List, pod_length::PodLength, primitives::PodU32},
5    bytemuck::Pod,
6    std::ops::Deref,
7};
8
9#[derive(Debug)]
10pub struct ListViewReadOnly<'data, T: Pod, L: PodLength = PodU32> {
11    pub(crate) length: &'data L,
12    pub(crate) data: &'data [T],
13    pub(crate) capacity: usize,
14}
15
16impl<T: Pod, L: PodLength> List for ListViewReadOnly<'_, T, L> {
17    type Item = T;
18    type Length = L;
19
20    fn capacity(&self) -> usize {
21        self.capacity
22    }
23}
24
25impl<T: Pod, L: PodLength> Deref for ListViewReadOnly<'_, T, L> {
26    type Target = [T];
27
28    fn deref(&self) -> &Self::Target {
29        let len = (*self.length).into();
30        &self.data[..len]
31    }
32}
33
34#[cfg(test)]
35mod tests {
36    use {
37        super::*,
38        crate::{
39            list::ListView,
40            pod_length::PodLength,
41            primitives::{PodU32, PodU64},
42        },
43        bytemuck_derive::{Pod as DerivePod, Zeroable},
44        std::mem::size_of,
45    };
46
47    #[repr(C, align(16))]
48    #[derive(DerivePod, Zeroable, Copy, Clone, Debug, PartialEq)]
49    struct TestStruct(u128);
50
51    /// Helper to build a byte buffer that conforms to the `ListView` layout.
52    fn build_test_buffer<T: Pod, L: PodLength>(
53        length: usize,
54        capacity: usize,
55        items: &[T],
56    ) -> Vec<u8> {
57        let size = ListView::<T, L>::size_of(capacity).unwrap();
58        let mut buffer = vec![0u8; size];
59
60        // Write the length prefix
61        let pod_len = L::try_from(length).unwrap();
62        let len_bytes = bytemuck::bytes_of(&pod_len);
63        buffer[0..size_of::<L>()].copy_from_slice(len_bytes);
64
65        // Write the data items, accounting for padding
66        if !items.is_empty() {
67            let data_start = ListView::<T, L>::size_of(0).unwrap();
68            let items_bytes = bytemuck::cast_slice(items);
69            buffer[data_start..data_start.saturating_add(items_bytes.len())]
70                .copy_from_slice(items_bytes);
71        }
72
73        buffer
74    }
75
76    #[test]
77    fn test_len_and_capacity() {
78        let items = [10u32, 20, 30];
79        let buffer = build_test_buffer::<u32, PodU32>(items.len(), 5, &items);
80        let view = ListView::<u32>::unpack(&buffer).unwrap();
81
82        assert_eq!(view.len(), 3);
83        assert_eq!(view.capacity(), 5);
84    }
85
86    #[test]
87    fn test_as_slice() {
88        let items = [10u32, 20, 30];
89        // Buffer has capacity for 5, but we only use 3.
90        let buffer = build_test_buffer::<u32, PodU32>(items.len(), 5, &items);
91        let view = ListView::<u32, PodU32>::unpack(&buffer).unwrap();
92
93        // `as_slice()` should only return the first `len` items.
94        assert_eq!(*view, items[..]);
95    }
96
97    #[test]
98    fn test_is_empty() {
99        // Not empty
100        let buffer_full = build_test_buffer::<u32, PodU32>(1, 2, &[10]);
101        let view_full = ListView::<u32>::unpack(&buffer_full).unwrap();
102        assert!(!view_full.is_empty());
103
104        // Empty
105        let buffer_empty = build_test_buffer::<u32, PodU32>(0, 2, &[]);
106        let view_empty = ListView::<u32>::unpack(&buffer_empty).unwrap();
107        assert!(view_empty.is_empty());
108    }
109
110    #[test]
111    fn test_iter() {
112        let items = [TestStruct(1), TestStruct(2)];
113        let buffer = build_test_buffer::<TestStruct, PodU64>(items.len(), 3, &items);
114        let view = ListView::<TestStruct, PodU64>::unpack(&buffer).unwrap();
115
116        let mut iter = view.iter();
117        assert_eq!(iter.next(), Some(&items[0]));
118        assert_eq!(iter.next(), Some(&items[1]));
119        assert_eq!(iter.next(), None);
120        let collected: Vec<_> = view.iter().collect();
121        assert_eq!(collected, vec![&items[0], &items[1]]);
122    }
123
124    #[test]
125    fn test_iter_on_empty_list() {
126        let buffer = build_test_buffer::<u32, PodU32>(0, 5, &[]);
127        let view = ListView::<u32, PodU32>::unpack(&buffer).unwrap();
128
129        assert_eq!(view.iter().count(), 0);
130        assert_eq!(view.iter().next(), None);
131    }
132
133    #[test]
134    fn test_zero_capacity() {
135        // Buffer is just big enough for the header (len + padding), no data.
136        let buffer = build_test_buffer::<TestStruct, PodU32>(0, 0, &[]);
137        let view = ListView::<TestStruct, PodU32>::unpack(&buffer).unwrap();
138
139        assert_eq!(view.len(), 0);
140        assert_eq!(view.capacity(), 0);
141        assert!(view.is_empty());
142        assert_eq!(*view, []);
143    }
144
145    #[test]
146    fn test_with_padding() {
147        // Test the effect of padding by checking the total header size.
148        // T=AlignedStruct (align 16), L=PodU32 (size 4).
149        // The header size should be 16 (4 for len + 12 for padding).
150        let header_size = ListView::<TestStruct>::size_of(0).unwrap();
151        assert_eq!(header_size, 16);
152
153        let items = [TestStruct(123), TestStruct(456)];
154        let buffer = build_test_buffer::<TestStruct, PodU32>(items.len(), 4, &items);
155        let view = ListView::<TestStruct>::unpack(&buffer).unwrap();
156
157        // Check if the public API works as expected despite internal padding
158        assert_eq!(view.len(), 2);
159        assert_eq!(view.capacity(), 4);
160        assert_eq!(*view, items[..]);
161    }
162
163    #[test]
164    fn test_bytes_used_and_allocated() {
165        // 3 live elements, capacity 5
166        let items = [10u32, 20, 30];
167        let capacity = 5;
168        let buffer = build_test_buffer::<u32, PodU32>(items.len(), capacity, &items);
169        let view = ListView::<u32>::unpack(&buffer).unwrap();
170
171        let expected_used = ListView::<u32>::size_of(view.len()).unwrap();
172        let expected_cap = ListView::<u32>::size_of(view.capacity()).unwrap();
173
174        assert_eq!(view.bytes_used().unwrap(), expected_used);
175        assert_eq!(view.bytes_allocated().unwrap(), expected_cap);
176    }
177
178    #[test]
179    fn test_get() {
180        let items = [10u32, 20, 30];
181        let buffer = build_test_buffer::<u32, PodU32>(items.len(), 5, &items);
182        let view = ListView::<u32>::unpack(&buffer).unwrap();
183
184        // Get in-bounds elements
185        assert_eq!(view.first(), Some(&10u32));
186        assert_eq!(view.get(1), Some(&20u32));
187        assert_eq!(view.get(2), Some(&30u32));
188
189        // Get out-of-bounds element (index == len)
190        assert_eq!(view.get(3), None);
191
192        // Get way out-of-bounds
193        assert_eq!(view.get(100), None);
194    }
195
196    #[test]
197    fn test_get_on_empty_list() {
198        let buffer = build_test_buffer::<u32, PodU32>(0, 5, &[]);
199        let view = ListView::<u32, PodU32>::unpack(&buffer).unwrap();
200        assert_eq!(view.first(), None);
201    }
202}