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