Skip to main content

spl_list_view/
list_view_read_only.rs

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