array_plus_extra/
lib.rs

1#![no_std]
2#![doc = include_str!("../README.md")]
3
4use core::ops::Deref;
5use core::ops::DerefMut;
6
7// Serde support (optional feature).
8#[cfg(feature = "serde")]
9mod serde_impl;
10
11// defmt support (optional feature).
12#[cfg(feature = "defmt")]
13mod defmt_impl;
14
15/// An array that holds N+EXTRA elements, where N and EXTRA is specified via const generic.
16#[repr(C)]
17#[derive(Debug, Clone, Copy)]
18#[cfg_attr(
19    feature = "postcard_max_size",
20    derive(postcard::experimental::max_size::MaxSize)
21)]
22pub struct ArrayPlusExtra<T, const N: usize, const EXTRA: usize> {
23    data: [T; N],
24    extra: [T; EXTRA],
25}
26
27impl<T, const N: usize, const EXTRA: usize> ArrayPlusExtra<T, N, EXTRA>
28where
29    T: Copy,
30{
31    /// Create a new array with specified value.
32    #[inline]
33    pub const fn new(value: T) -> Self {
34        Self {
35            data: [value; N],
36            extra: [value; EXTRA],
37        }
38    }
39}
40
41impl<T, const N: usize, const EXTRA: usize> ArrayPlusExtra<T, N, EXTRA> {
42    /// Get a slice view of all N+EXTRA elements.
43    /// This is a const fn that can be used in const contexts.
44    #[inline]
45    pub const fn as_slice(&self) -> &[T] {
46        // SAFETY: #[repr(C)] guarantees `data` and `extra` are contiguous in memory.
47        // Pointer to first element and length N+EXTRA creates a valid slice with correct lifetime.
48        unsafe { core::slice::from_raw_parts(self as *const _ as *const T, N + EXTRA) }
49    }
50
51    /// Get a mutable slice view of all N+EXTRA elements.
52    /// This is a const fn that can be used in const contexts.
53    #[inline]
54    pub const fn as_mut_slice(&mut self) -> &mut [T] {
55        // SAFETY: #[repr(C)] guarantees `data` and `extra` are contiguous in memory.
56        // Pointer to first element and length N+EXTRA creates a valid slice with correct lifetime.
57        unsafe { core::slice::from_raw_parts_mut(self as *mut _ as *mut T, N + EXTRA) }
58    }
59}
60
61impl<T, const N: usize, const EXTRA: usize> Deref for ArrayPlusExtra<T, N, EXTRA> {
62    type Target = [T];
63
64    #[inline(always)]
65    fn deref(&self) -> &Self::Target {
66        self.as_slice()
67    }
68}
69
70impl<T, const N: usize, const EXTRA: usize> DerefMut for ArrayPlusExtra<T, N, EXTRA> {
71    #[inline(always)]
72    fn deref_mut(&mut self) -> &mut Self::Target {
73        self.as_mut_slice()
74    }
75}
76
77// Forward PartialEq to slice implementation.
78impl<T, const N: usize, const EXTRA: usize> PartialEq for ArrayPlusExtra<T, N, EXTRA>
79where
80    T: PartialEq,
81{
82    #[inline]
83    fn eq(&self, other: &Self) -> bool {
84        self[..] == other[..]
85    }
86}
87
88// Forward Eq to slice implementation.
89impl<T, const N: usize, const EXTRA: usize> Eq for ArrayPlusExtra<T, N, EXTRA> where T: Eq {}
90
91// Forward Hash to slice implementation.
92impl<T, const N: usize, const EXTRA: usize> core::hash::Hash for ArrayPlusExtra<T, N, EXTRA>
93where
94    T: core::hash::Hash,
95{
96    #[inline]
97    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
98        self[..].hash(state);
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    // Use std for tests only.
107    extern crate std;
108    use std::format;
109
110    // Tests for EXTRA = 0 (no extra elements).
111    #[test]
112    fn test_extra_zero_with_n_zero() {
113        let arr: ArrayPlusExtra<i32, 0, 0> = ArrayPlusExtra::new(42);
114        // Total length should be N + EXTRA = 0 + 0 = 0.
115        assert_eq!(arr.len(), 0);
116    }
117
118    #[test]
119    fn test_extra_zero_with_n_five() {
120        let mut arr: ArrayPlusExtra<i32, 5, 0> = ArrayPlusExtra::new(42);
121        // Total length should be N + EXTRA = 5 + 0 = 5.
122        assert_eq!(arr.len(), 5);
123        // Access all N elements through deref - Miri will catch UB.
124        for i in 0..5 {
125            assert_eq!(arr[i], 42);
126        }
127        // Mutate elements.
128        arr[4] = 100;
129        assert_eq!(arr[4], 100);
130    }
131
132    // Tests for EXTRA = 1 (classic plus one).
133    #[test]
134    fn test_extra_one_with_n_zero() {
135        let mut arr: ArrayPlusExtra<i32, 0, 1> = ArrayPlusExtra::new(99);
136        // Total length should be N + EXTRA = 0 + 1 = 1.
137        assert_eq!(arr.len(), 1);
138        assert_eq!(arr[0], 99);
139        arr[0] = 100;
140        assert_eq!(arr[0], 100);
141    }
142
143    #[test]
144    fn test_extra_one_with_n_five() {
145        let arr: ArrayPlusExtra<i32, 5, 1> = ArrayPlusExtra::new(42);
146        // Total length should be N + EXTRA = 5 + 1 = 6.
147        assert_eq!(arr.len(), 6);
148        // Access all N+1 elements through deref - Miri will catch UB.
149        for i in 0..6 {
150            assert_eq!(arr[i], 42);
151        }
152    }
153
154    #[test]
155    fn test_extra_one_deref_mut() {
156        let mut arr: ArrayPlusExtra<i32, 4, 1> = ArrayPlusExtra::new(0);
157        // Total length = 4 + 1 = 5.
158        assert_eq!(arr.len(), 5);
159        // Write to all N+EXTRA elements - Miri will catch if memory layout is wrong.
160        for i in 0..5 {
161            arr[i] = i as i32;
162        }
163        // Read them back.
164        for i in 0..5 {
165            assert_eq!(arr[i], i as i32);
166        }
167    }
168
169    // Tests for larger EXTRA values.
170    #[test]
171    fn test_extra_three_with_n_two() {
172        let mut arr: ArrayPlusExtra<u32, 2, 3> = ArrayPlusExtra::new(7);
173        // Total length should be N + EXTRA = 2 + 3 = 5.
174        assert_eq!(arr.len(), 5);
175        // Access all elements.
176        for i in 0..5 {
177            assert_eq!(arr[i], 7);
178        }
179        // Modify elements across both data and extra regions.
180        arr[0] = 10; // In data array.
181        arr[1] = 20; // In data array.
182        arr[2] = 30; // In extra array (first).
183        arr[3] = 40; // In extra array (middle).
184        arr[4] = 50; // In extra array (last).
185
186        assert_eq!(arr[0], 10);
187        assert_eq!(arr[1], 20);
188        assert_eq!(arr[2], 30);
189        assert_eq!(arr[3], 40);
190        assert_eq!(arr[4], 50);
191    }
192
193    #[test]
194    fn test_extra_ten_with_n_ten() {
195        let mut arr: ArrayPlusExtra<u64, 10, 10> = ArrayPlusExtra::new(0);
196        // Total length should be N + EXTRA = 10 + 10 = 20.
197        assert_eq!(arr.len(), 20);
198        // Interleaved reads and writes to stress test the unsafe code.
199        arr[0] = 1;
200        arr[9] = 9; // Last of data array.
201        arr[10] = 10; // First of extra array.
202        arr[19] = 19; // Last of extra array.
203
204        assert_eq!(arr[0], 1);
205        assert_eq!(arr[9], 9);
206        assert_eq!(arr[10], 10);
207        assert_eq!(arr[19], 19);
208    }
209
210    #[test]
211    fn test_extra_fifty_large() {
212        let mut arr: ArrayPlusExtra<u8, 50, 50> = ArrayPlusExtra::new(1);
213        // Total length = 50 + 50 = 100.
214        assert_eq!(arr.len(), 100);
215        // Access first, middle (in data), middle (in extra), and last elements.
216        assert_eq!(arr[0], 1);
217        assert_eq!(arr[25], 1); // Middle of data.
218        assert_eq!(arr[49], 1); // Last of data.
219        assert_eq!(arr[50], 1); // First of extra.
220        assert_eq!(arr[75], 1); // Middle of extra.
221        assert_eq!(arr[99], 1); // Last of extra.
222
223        arr[99] = 255;
224        assert_eq!(arr[99], 255);
225    }
226
227    // Tests with different types and EXTRA values.
228    #[test]
229    fn test_different_types_various_extra() {
230        // Test with u8, EXTRA=2.
231        let arr_u8: ArrayPlusExtra<u8, 2, 2> = ArrayPlusExtra::new(255);
232        assert_eq!(arr_u8.len(), 4);
233        assert_eq!(arr_u8[3], 255);
234
235        // Test with u64, EXTRA=3.
236        let arr_u64: ArrayPlusExtra<u64, 2, 3> = ArrayPlusExtra::new(u64::MAX);
237        assert_eq!(arr_u64.len(), 5);
238        assert_eq!(arr_u64[4], u64::MAX);
239
240        // Test with f64, EXTRA=5.
241        let mut arr_f64: ArrayPlusExtra<f64, 1, 5> = ArrayPlusExtra::new(1.5);
242        assert_eq!(arr_f64.len(), 6);
243        arr_f64[5] = 2.5;
244        assert_eq!(arr_f64[5], 2.5);
245    }
246
247    #[test]
248    fn test_slice_iteration_various_extra() {
249        // EXTRA=1.
250        let mut arr: ArrayPlusExtra<i32, 3, 1> = ArrayPlusExtra::new(0);
251        assert_eq!(arr.len(), 4);
252        arr[0] = 10;
253        arr[1] = 20;
254        arr[2] = 30;
255        arr[3] = 40;
256
257        // Iterate using slice methods - exercises deref.
258        let sum: i32 = arr.iter().sum();
259        assert_eq!(sum, 100);
260
261        // Mutable iteration - exercises deref_mut.
262        for elem in arr.iter_mut() {
263            *elem += 1;
264        }
265        assert_eq!(arr[0], 11);
266        assert_eq!(arr[3], 41);
267
268        // EXTRA=5.
269        let mut arr2: ArrayPlusExtra<i32, 2, 5> = ArrayPlusExtra::new(1);
270        assert_eq!(arr2.len(), 7);
271        let sum2: i32 = arr2.iter().sum();
272        assert_eq!(sum2, 7);
273
274        for elem in arr2.iter_mut() {
275            *elem *= 2;
276        }
277        for i in 0..7 {
278            assert_eq!(arr2[i], 2);
279        }
280    }
281
282    // Tests for trait implementations.
283    #[test]
284    fn test_debug() {
285        let arr: ArrayPlusExtra<i32, 2, 1> = ArrayPlusExtra::new(42);
286        let debug_str = format!("{:?}", arr);
287        // Should format like a slice.
288        assert!(debug_str.contains("42"));
289    }
290
291    #[test]
292    #[allow(clippy::clone_on_copy)]
293    fn test_clone() {
294        let arr: ArrayPlusExtra<i32, 2, 2> = ArrayPlusExtra::new(5);
295        let cloned = arr.clone();
296        assert_eq!(arr[0], cloned[0]);
297        assert_eq!(arr.len(), cloned.len());
298        for i in 0..4 {
299            assert_eq!(arr[i], cloned[i]);
300        }
301    }
302
303    #[test]
304    fn test_partial_eq() {
305        let arr1: ArrayPlusExtra<i32, 2, 1> = ArrayPlusExtra::new(42);
306        let arr2: ArrayPlusExtra<i32, 2, 1> = ArrayPlusExtra::new(42);
307        let arr3: ArrayPlusExtra<i32, 2, 1> = ArrayPlusExtra::new(99);
308
309        // Equal arrays should be equal.
310        assert_eq!(arr1, arr2);
311        // Different values should not be equal.
312        assert_ne!(arr1, arr3);
313    }
314
315    #[test]
316    fn test_eq_reflexive() {
317        let arr: ArrayPlusExtra<i32, 3, 2> = ArrayPlusExtra::new(7);
318        assert_eq!(arr, arr);
319    }
320
321    #[test]
322    fn test_hash() {
323        use core::hash::{Hash, Hasher};
324        use std::collections::hash_map::DefaultHasher;
325
326        let arr1: ArrayPlusExtra<i32, 2, 1> = ArrayPlusExtra::new(42);
327        let arr2: ArrayPlusExtra<i32, 2, 1> = ArrayPlusExtra::new(42);
328        let arr3: ArrayPlusExtra<i32, 2, 1> = ArrayPlusExtra::new(99);
329
330        let mut hasher1 = DefaultHasher::new();
331        let mut hasher2 = DefaultHasher::new();
332        let mut hasher3 = DefaultHasher::new();
333
334        arr1.hash(&mut hasher1);
335        arr2.hash(&mut hasher2);
336        arr3.hash(&mut hasher3);
337
338        // Equal values should have equal hashes.
339        assert_eq!(hasher1.finish(), hasher2.finish());
340        // Different values should (usually) have different hashes.
341        assert_ne!(hasher1.finish(), hasher3.finish());
342    }
343
344    #[test]
345    fn test_copy() {
346        let arr: ArrayPlusExtra<i32, 2, 1> = ArrayPlusExtra::new(10);
347        let copied = arr; // Should use Copy, not Clone.
348        // Original should still be usable.
349        assert_eq!(arr[0], 10);
350        assert_eq!(copied[0], 10);
351    }
352
353    // Tests for const fn methods.
354    #[test]
355    fn test_as_slice_const_fn() {
356        const ARR: ArrayPlusExtra<i32, 2, 1> = ArrayPlusExtra::new(42);
357        const SLICE: &[i32] = ARR.as_slice();
358        const LEN: usize = SLICE.len();
359
360        assert_eq!(LEN, 3);
361        assert_eq!(SLICE[0], 42);
362        assert_eq!(SLICE[1], 42);
363        assert_eq!(SLICE[2], 42);
364    }
365
366    #[test]
367    fn test_const_creation_and_slicing() {
368        const ARR: ArrayPlusExtra<u8, 5, 3> = ArrayPlusExtra::new(255);
369        const SLICE: &[u8] = ARR.as_slice();
370        const FIRST: u8 = SLICE[0];
371        const LAST_INDEX: usize = SLICE.len() - 1;
372
373        assert_eq!(FIRST, 255);
374        assert_eq!(SLICE.len(), 8);
375        assert_eq!(SLICE[LAST_INDEX], 255);
376    }
377
378    #[test]
379    fn test_as_slice_method() {
380        let arr: ArrayPlusExtra<i32, 3, 2> = ArrayPlusExtra::new(7);
381        let slice = arr.as_slice();
382
383        assert_eq!(slice.len(), 5);
384        for &val in slice {
385            assert_eq!(val, 7);
386        }
387    }
388
389    #[test]
390    fn test_as_mut_slice_method() {
391        let mut arr: ArrayPlusExtra<i32, 2, 2> = ArrayPlusExtra::new(0);
392        let slice = arr.as_mut_slice();
393
394        slice[0] = 10;
395        slice[1] = 20;
396        slice[2] = 30;
397        slice[3] = 40;
398
399        assert_eq!(arr[0], 10);
400        assert_eq!(arr[1], 20);
401        assert_eq!(arr[2], 30);
402        assert_eq!(arr[3], 40);
403    }
404
405    #[test]
406    fn test_const_zero_sized() {
407        const ARR: ArrayPlusExtra<i32, 0, 0> = ArrayPlusExtra::new(42);
408        const SLICE: &[i32] = ARR.as_slice();
409        const LEN: usize = SLICE.len();
410
411        assert_eq!(LEN, 0);
412    }
413}