static_slicing/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2// Disable Clippy's let_unit_value warning because if we take its suggestion
3// we just get a different warning.
4#![allow(clippy::let_unit_value)]
5
6//! Provides utilities for emulating statically-checked array slicing and indexing.
7//!
8//! The [`StaticRangeIndex`] type can be used as an index into fixed-size arrays
9//! to get or set a fixed-size slice. Likewise, the [`StaticIndex`] type can be used 
10//! to get or set the element at a given index.
11//! 
12//! The static index types can be used to index other collections, such as slices and [`Vec`]s,
13//! but only when they are wrapped in a [`SliceWrapper`]. Due to Rust's orphan rule and lack of
14//! specialization support, this seems to be the best we can do - it's apparently impossible to
15//! implement [`Index`] for both `[T; N]` and `[T]`.
16//! 
17//! # Examples
18//!
19//! This example demonstrates how to obtain an 8-element slice of an array, starting from index 4.
20//! ```
21//! use static_slicing::StaticRangeIndex;
22//! let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
23//! let sub_arr = arr[StaticRangeIndex::<4, 8>];
24//! assert_eq!(sub_arr, arr[4..12]);
25//! ```
26//!
27//! This example demonstrates how to obtain a mutable 3-element slice of an array, starting from index 2.
28//! ```
29//! use static_slicing::StaticRangeIndex;
30//! let mut arr = [3, 5, 7, 9, 11];
31//! let sub_arr = &mut arr[StaticRangeIndex::<2, 3>];
32//! sub_arr[1] = 13;
33//! assert_eq!(arr[3], 13);
34//! ```
35//!
36//! This example demonstrates how to obtain the item at index 2 of a 4-element array.
37//! ```
38//! use static_slicing::StaticIndex;
39//! let arr = [3, 5, 7, 9];
40//! let value = arr[StaticIndex::<2>];
41//! assert_eq!(value, 7);
42//! ```
43//! 
44//! This example demonstrates how to obtain a 2-element slice of a 4-element [`Vec`] 
45//! starting from index 0.
46//! ```
47//! use static_slicing::{SliceWrapper, StaticRangeIndex};
48//! let x = vec![1, 2, 3];
49//! let x = SliceWrapper::new(x);
50//! assert_eq!(x[StaticRangeIndex::<0, 2>], [1, 2]);
51//! ```
52//!
53//! The following examples demonstrate the compile-time safety guarantees of the static slicing framework.
54//! ```compile_fail
55//! use static_slicing::StaticRangeIndex;
56//! let arr = [1, 2, 3, 4, 5];
57//! // error! we can't get 5 elements starting from index 1
58//! let sub_arr = arr[StaticRangeIndex::<1, 5>];
59//! ```
60//!
61//! ```compile_fail
62//! use static_slicing::StaticIndex;
63//! let arr = [1, 2, 3, 4, 5];
64//! // error! we can't get the item at index 5, because there are only 5 items
65//! let value = arr[StaticIndex::<5>];
66//! ```
67//! 
68//! The following examples demonstrate the runtime safety guarantees of the static slicing framework.
69//! Note that when fixed-size arrays are used, there is zero cost at runtime, since all checks are done
70//! at compile time.
71//! 
72//! ```should_panic
73//! use static_slicing::{SliceWrapper, StaticIndex};
74//! let x = SliceWrapper::new(vec![1, 2, 3]);
75//! let _ = x[StaticIndex::<3>];
76//! ```
77//! ```should_panic
78//! use static_slicing::{SliceWrapper, StaticIndex};
79//! let mut x = SliceWrapper::new(vec![1, 2, 3]);
80//! x[StaticIndex::<3>] = 5;
81//! ```
82use core::{
83    marker::PhantomData, 
84    ops::{Index, IndexMut, Deref, DerefMut},
85};
86
87/// Internal helper trait for static indexing.
88///
89/// [`IsValidIndex::RESULT`] must evaluate to `()` if the index is valid,
90/// or panic otherwise.
91trait IsValidIndex<const INDEX: usize> {
92    const RESULT: ();
93}
94
95/// A single-element index that exists entirely at compile time.
96pub struct StaticIndex<const INDEX: usize>;
97
98impl<T, const INDEX: usize, const N: usize> IsValidIndex<INDEX> for [T; N] {
99    const RESULT: () = assert!(N > INDEX, "Index is out of bounds!");
100}
101
102impl<T, const INDEX: usize, const N: usize> Index<StaticIndex<INDEX>> for [T; N] {
103    type Output = T;
104
105    fn index(&self, _: StaticIndex<INDEX>) -> &Self::Output {
106        let _ = <[T; N] as IsValidIndex<INDEX>>::RESULT;
107
108        // SAFETY: We've verified bounds at compile time.
109        unsafe { &*(self.as_ptr().add(INDEX) as *const T) }
110    }
111}
112
113impl<T, const INDEX: usize, const N: usize> IndexMut<StaticIndex<INDEX>> for [T; N] {
114    fn index_mut(&mut self, _: StaticIndex<INDEX>) -> &mut Self::Output {
115        let _ = <[T; N] as IsValidIndex<INDEX>>::RESULT;
116
117        // SAFETY: We've verified bounds at compile time.
118        unsafe { &mut *(self.as_mut_ptr().add(INDEX) as *mut T) }
119    }
120}
121
122/// Internal helper trait for static range indexing.
123///
124/// [`IsValidRangeIndex::RESULT`] must evaluate to `()` if the range is valid,
125/// or panic otherwise.
126trait IsValidRangeIndex<const START: usize, const LENGTH: usize> {
127    const RESULT: ();
128}
129
130/// A range index that exists entirely at compile time.
131/// Range indexes can be used to obtain fixed-size slices.
132/// For any pair of `(START, LENGTH)`, the range covered is `[START, START+LENGTH)`.
133pub struct StaticRangeIndex<const START: usize, const LENGTH: usize>;
134
135impl<T, const START: usize, const LENGTH: usize, const N: usize> IsValidRangeIndex<START, LENGTH>
136    for [T; N]
137{
138    const RESULT: () = {
139        assert!(N > START, "Starting index is out of bounds!");
140        assert!(N - START >= LENGTH, "Ending index is out of bounds! Check your range index's length.");
141    };
142}
143
144impl<T, const START: usize, const LENGTH: usize, const N: usize>
145    Index<StaticRangeIndex<START, LENGTH>> for [T; N]
146{
147    type Output = [T; LENGTH];
148
149    fn index(&self, _: StaticRangeIndex<START, LENGTH>) -> &Self::Output {
150        let _ = <[T; N] as IsValidRangeIndex<START, LENGTH>>::RESULT;
151
152        // SAFETY: We've verified bounds at compile time.
153        unsafe { &*(self.as_ptr().add(START) as *const [T; LENGTH]) }
154    }
155}
156
157impl<T, const START: usize, const LENGTH: usize, const N: usize>
158    IndexMut<StaticRangeIndex<START, LENGTH>> for [T; N]
159{
160    fn index_mut(&mut self, _: StaticRangeIndex<START, LENGTH>) -> &mut Self::Output {
161        let _ = <[T; N] as IsValidRangeIndex<START, LENGTH>>::RESULT;
162
163        // SAFETY: We've verified bounds at compiile time.
164        unsafe { &mut *(self.as_mut_ptr().add(START) as *mut [T; LENGTH]) }
165    }
166}
167
168/// Wrapper around slice references to add support for
169/// the static index types.
170/// 
171/// Due to language weirdness, we can't implement [`Index`] and [`IndexMut`]
172/// with static indexes for both `[T]` and `[T; N]`. As a result, we need this 
173/// wrapper type.
174/// 
175/// For convenience, [`SliceWrapper`] also implements [`Deref`] and [`DerefMut`].
176/// This allows [`SliceWrapper`] instances to be converted to `&[T]`, in addition to
177/// allowing all the underlying functions of `T` to be used.
178#[repr(transparent)]
179pub struct SliceWrapper<'a, I, T>(
180    /// The actual data reference.
181    T,
182
183    /// Informs the compiler that the lifetime 'a
184    /// is actually part of the type.
185    PhantomData<&'a ()>,
186
187    /// Informs the compiler that the type parameter I
188    /// is actually part of the type. The reason we need
189    /// this is so that we can use an AsRef bound without
190    /// the compiler throwing an E0207 at us regarding I.
191    PhantomData<I>
192);
193
194impl<'a, I, T> SliceWrapper<'a, I, T> where T: AsRef<[I]> {
195    pub fn new(data: T) -> Self {
196        Self(data, PhantomData, PhantomData)
197    }
198}
199
200impl<'a, I, T> Deref for SliceWrapper<'a, I, T> {
201    type Target = T;
202    fn deref(&self) -> &Self::Target {
203        &self.0
204    }
205}
206
207impl<'a, I, T> DerefMut for SliceWrapper<'a, I, T> {
208    fn deref_mut(&mut self) -> &mut Self::Target {
209        &mut self.0
210    }
211}
212
213impl<I, S: AsRef<[I]>, const START: usize, const LENGTH: usize> Index<StaticRangeIndex<START, LENGTH>> for SliceWrapper<'_, I, S> {
214    type Output = [I; LENGTH];
215
216    fn index(&self, _: StaticRangeIndex<START, LENGTH>) -> &Self::Output {
217        let inner: &[I] = self.0.as_ref();
218
219        assert!(inner.len() > START, "Starting index {} is out of bounds", START);
220        assert!(inner.len() - START >= LENGTH, "Not enough items after index {} (requested {}; length: {})", START, LENGTH, inner.len());
221
222        // SAFETY: We've verified bounds at runtime.
223        unsafe { &*(inner.as_ptr().add(START) as *const [I; LENGTH]) }
224    }
225}
226
227impl<I, S: AsRef<[I]>, const INDEX: usize> Index<StaticIndex<INDEX>> for SliceWrapper<'_, I, S> {
228    type Output = I;
229
230    fn index(&self, _: StaticIndex<INDEX>) -> &Self::Output {
231        self.0.as_ref().index(INDEX)
232    }
233}
234
235impl<I, S: AsRef<[I]> + AsMut<[I]>, const START: usize, const LENGTH: usize> IndexMut<StaticRangeIndex<START, LENGTH>> for SliceWrapper<'_, I, S> {
236    fn index_mut(&mut self, _: StaticRangeIndex<START, LENGTH>) -> &mut Self::Output {
237        let inner: &mut [I] = self.0.as_mut();
238
239        assert!(inner.len() > START, "Starting index {} is out of bounds", START);
240        assert!(inner.len() - START >= LENGTH, "Not enough items after index {} (requested {}; length: {})", START, LENGTH, inner.len());
241
242        // SAFETY: We've verified bounds at runtime.
243        unsafe { &mut *(inner.as_mut_ptr().add(START) as *mut [I; LENGTH]) }
244    }
245}
246
247impl<I, S: AsRef<[I]> + AsMut<[I]>, const INDEX: usize> IndexMut<StaticIndex<INDEX>> for SliceWrapper<'_, I, S> {
248    fn index_mut(&mut self, _: StaticIndex<INDEX>) -> &mut Self::Output {
249        self.0.as_mut().index_mut(INDEX)
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    mod core_functionality {
258        use super::*;
259
260        #[test]
261        fn test_immutable_static_slice() {
262            let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
263            let sub_arr = arr[StaticRangeIndex::<4, 8>];
264    
265            assert_eq!(sub_arr, arr[4..12]);
266        }
267    
268        #[test]
269        fn test_mutable_static_slice() {
270            let mut arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
271            let sub_arr = &mut arr[StaticRangeIndex::<4, 8>];
272    
273            sub_arr[0] = 1234;
274            assert_eq!(arr[4], 1234);
275        }
276    
277        #[test]
278        fn test_full_immutable_static_slice() {
279            let arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
280            let sub_arr = arr[StaticRangeIndex::<0, 12>];
281    
282            assert_eq!(arr, sub_arr);
283        }
284    
285        #[test]
286        fn test_full_mutable_static_slice() {
287            let mut arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
288            let sub_arr = &mut arr[StaticRangeIndex::<0, 12>];
289    
290            sub_arr[4] = 5;
291            sub_arr[5] = 4;
292            assert_eq!(arr[4], 5);
293            assert_eq!(arr[5], 4);
294        }
295    
296        #[test]
297        fn test_immutable_static_index() {
298            let arr = [1, 2, 3, 4, 5];
299            assert_eq!(arr[StaticIndex::<4>], 5);
300        }
301    
302        #[test]
303        fn test_mutable_static_index() {
304            let mut arr = [1, 2, 3, 4, 5];
305            arr[StaticIndex::<4>] = 6;
306            assert_eq!(arr, [1, 2, 3, 4, 6]);
307        }
308    }
309
310    mod wrapper_functionality {
311        use super::*;
312
313        #[test]
314        fn test_wrapped_slice_read_single() {
315            let x = SliceWrapper::new(&[1, 2, 3]);
316            assert_eq!(x[StaticIndex::<2>], 3);
317            assert_eq!(x.len(), 3);
318        }
319
320        #[test]
321        fn test_wrapped_slice_write_single() {
322            let mut x =  [1, 2, 3];
323            let mut y = SliceWrapper::new(&mut x);
324            y[StaticIndex::<2>] = 5;
325            assert_eq!(x[2], 5);
326        }
327
328        #[test]
329        fn test_wrapped_slice_read_multi() {
330            let x = SliceWrapper::new(&[1, 2, 3]);
331            assert_eq!(x[StaticRangeIndex::<0, 2>], [1, 2]);
332        }
333
334        #[test]
335        fn test_wrapped_slice_write_multi() {
336            let mut x =  [1, 2, 3];
337            let mut y = SliceWrapper::new(&mut x);
338            y[StaticRangeIndex::<0, 2>] = [3, 4];
339            assert_eq!(x, [3, 4, 3]);
340        }
341
342        #[test]
343        fn test_wrapped_vec_read() {
344            let x = vec![1, 2, 3];
345            let x = SliceWrapper::new(x);
346            assert_eq!(x[StaticRangeIndex::<0, 2>], [1, 2]);
347            assert_eq!(x.len(), 3);
348        }
349
350        #[test]
351        fn test_wrapped_vec_write() {
352            let mut x = vec![1, 2, 3];
353            let mut y = SliceWrapper::new(&mut x);
354            y[StaticRangeIndex::<1, 2>] = [4, 5];
355
356            assert_eq!(y[StaticRangeIndex::<0, 3>], [1, 4, 5]);
357            assert_eq!(x[0..3], [1, 4, 5]);
358        }
359    }
360
361    mod wrapper_safety {
362        use super::*;
363
364        #[test]
365        #[should_panic]
366        fn wrapped_slice_oob_read_should_panic() {
367            let x = SliceWrapper::new(&[1, 2, 3]);
368            let _ = x[StaticIndex::<3>];
369        }
370    
371        #[test]
372        #[should_panic]
373        fn wrapped_slice_oob_write_should_panic() {
374            let mut x = [1, 2, 3];
375            let mut x = SliceWrapper::new(&mut x);
376            x[StaticIndex::<3>] = 1337;
377        }
378        
379        #[test]
380        #[should_panic]
381        fn wrapped_slice_oob_range_read_should_panic() {
382            let x = SliceWrapper::new(&[1, 2, 3]);
383            let _ = x[StaticRangeIndex::<0, 5>];
384        }
385    
386        #[test]
387        #[should_panic]
388        fn wrapped_slice_oob_range_write_should_panic() {
389            let mut x = [1, 2, 3];
390            let mut x = SliceWrapper::new(&mut x);
391            x[StaticRangeIndex::<0, 5>] = [2, 3, 4, 5, 6];
392        }
393    
394        #[test]
395        #[should_panic]
396        fn wrapped_vec_oob_read_should_panic() {
397            let x = SliceWrapper::new(vec![1, 2, 3]);
398            let _ = x[StaticIndex::<3>];
399        }
400    
401        #[test]
402        #[should_panic]
403        fn wrapped_vec_oob_write_should_panic() {
404            let mut x = SliceWrapper::new(vec![1, 2, 3]);
405            x[StaticIndex::<3>] = 1337;
406        }
407    
408        #[test]
409        #[should_panic]
410        fn wrapped_vec_oob_range_read_should_panic() {
411            let x = SliceWrapper::new(vec![1, 2, 3]);
412            let _ = x[StaticRangeIndex::<0, 5>];
413        }
414    
415        #[test]
416        #[should_panic]
417        fn wrapped_vec_oob_range_write_should_panic() {
418            let mut x = SliceWrapper::new(vec![1, 2, 3]);
419            x[StaticRangeIndex::<0, 5>] = [2, 3, 4, 5, 6];
420        }
421    }
422}