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