├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches ├── static_index_benchmark.rs └── static_range_index_benchmark.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "static-slicing" 3 | version = "0.2.0" 4 | edition = "2021" 5 | authors = ["Kai Kaufman "] 6 | rust-version = "1.59" 7 | description = "Utilities for enhanced slicing and indexing" 8 | repository = "https://github.com/ktkaufman03/static-slicing" 9 | license = "BSD-3-Clause" 10 | keywords = ["slice", "index"] 11 | categories = ["no-std"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | 17 | [dev-dependencies] 18 | criterion = { version = "0.4", features = ["html_reports"] } 19 | 20 | [[bench]] 21 | name = "static_index_benchmark" 22 | harness = false 23 | 24 | [[bench]] 25 | name = "static_range_index_benchmark" 26 | harness = false 27 | 28 | [features] 29 | default = ["std"] 30 | std = [] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, Kai Kaufman 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Utilities for enhanced slicing and indexing 2 | === 3 | 4 | ## Introduction 5 | The `static_slicing` library provides a set of helpful utilities for compile-time checked slicing and indexing. 6 | 7 | ## Background 8 | 9 | Not interested in reading all this? Here's a TL;DR: slicing and indexing can be weird in some fairly common situations, so this library makes those things less weird. Now you can [skip to the Installation section.](https://github.com/ktkaufman03/static-slicing#installation) 10 | 11 | I initially developed this library for use by the [Worcester Polytechnic Institute](https://wpi.edu)'s team in the 2023 [MITRE Embedded Capture the Flag](https://ectf.mitre.org) competition. 12 | 13 | During development of the firmware that was to be installed on an embedded system, an interesting language pain point was identified: insufficient compile-time inference surrounding array slicing and indexing. 14 | 15 | For example, this function fails to compile ([try it yourself](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=492f9d73611549f07b99d2b9df218791)): 16 | ```rs 17 | fn test() { 18 | let a = [0u8, 1u8, 2u8, 3u8]; 19 | let x = a[4]; // there are only 4 elements in `a`! no good... 20 | } 21 | ``` 22 | Thankfully, the compiler knows that accessing index 4 of a 4-element _array_ will never succeed, and raises a `unconditional_panic` warning that is turned into an error by default. 23 | 24 | However, this nearly-identical function compiles just fine, and crashes at runtime (again, [try it yourself](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=301fc4147a9fc2d4716c7bc6158fe4a1)): 25 | ```rs 26 | fn test() { 27 | let a = &[0u8, 1u8, 2u8, 3u8]; 28 | let x = a[4]; // there are only 4 elements in `a`! no good... 29 | } 30 | ``` 31 | 32 | The compiler _knows_ that `a` is a reference to a 4-element array of `u8` - in other words, `&[u8; 4]` - yet is no longer able to indicate that anything is potentially wrong. 33 | 34 | The compiler's analysis (or lack thereof) of slicing can also be problematic. This function, which a new user might expect to compile, actually _doesn't_ compile: 35 | ```rs 36 | fn test() { 37 | let a = &[0u8, 1u8, 2u8, 3u8]; 38 | let x: &[u8; 2] = &a[1..3]; // *x should be [a[1], a[2]], which is a 2-element array. 39 | } 40 | ``` 41 | 42 | The compiler's explanation: 43 | ``` 44 | 3 | let x: &[u8; 2] = &a[1..3]; 45 | | -------- ^^^^^^^^ expected array `[u8; 2]`, found slice `[u8]` 46 | | | 47 | | expected due to this 48 | ``` 49 | 50 | This doesn't work, either: 51 | ```rs 52 | fn test() { 53 | let a = &[0u8, 1u8, 2u8, 3u8]; 54 | let x: &[u8; 2] = &a[1..3].into(); 55 | } 56 | ``` 57 | 58 | However, _this_ does: 59 | ```rs 60 | fn test() { 61 | let a = &[0u8, 1u8, 2u8, 3u8]; 62 | let x: &[u8; 2] = &a[1..3].try_into().unwrap(); 63 | } 64 | ``` 65 | 66 | I am not a huge fan of throwing `.try_into().unwrap()` at the end of everything, but I _am_ a huge fan of solving weird problems like this - that's why this library exists! 67 | 68 | ## Installation 69 | 70 | To install the current version of `static_slicing`, add the following to the `dependencies` section of your `Cargo.toml` file: 71 | 72 | ```toml 73 | static-slicing = "0.2.0" 74 | ``` 75 | 76 | `no_std` support can be enabled by disabling default features: 77 | 78 | ```toml 79 | static-slicing = { version = "0.2.0", default-features = false } 80 | ``` 81 | 82 | **Note: This library requires Rust 1.59 or later.** 83 | 84 | ## How does it work? 85 | 86 | Don't want to read this? [Skip to the Examples section.](https://github.com/ktkaufman03/static-slicing#examples) 87 | 88 | This library introduces two new index types that leverage the power of [const generics](https://doc.rust-lang.org/reference/items/generics.html#const-generics): 89 | - `StaticIndex` for getting/setting _individual_ items, and; 90 | - `StaticRangeIndex` for getting/setting _ranges_ of items. 91 | 92 | For fixed-size arrays (i.e., `[T; N]` where `T` is the element type and `N` is the array length), the library provides implementations of `Index` and `IndexMut` that accept both of the static index types. With [const panic](https://rust-lang.github.io/rfcs/2345-const-panic.html) support (and a bit of type system hacking), invalid indexing operations can be prevented from compiling altogether! 93 | 94 | _To see exactly how this is done, look at the implementations of `IsValidIndex` and `IsValidRangeIndex`._ 95 | 96 | For other types (slices, `Vec`s, etc.), use of `SliceWrapper` is necessary. `SliceWrapper` is an unfortunate workaround for some issues with Rust's orphan rules that I encountered during development. Regardless, `SliceWrapper` is designed to appear as "normal" as possible. It can be passed to functions that accept slice references, and essentially appears to be the data it's wrapping. 97 | 98 | ## Performance 99 | 100 | [Criterion](https://github.com/bheisler/criterion.rs) benchmarks are included for fixed-size array indexing. On my computer, which has a 12-core AMD Ryzen 9 5900X, no significant _negative_ performance difference was observed between the built-in indexing facilities and those provided by this library. That is to say, this won't make your code 200000x slower - in fact, any performance impact should be negligible at worst. 101 | 102 | Here are the raw numbers from a single benchmark run: 103 | 104 | | benchmark type | compile-time checked | runtime checked | 105 | | -------------- | -------------------- | --------------- | 106 | | single index | 428.87 ps | 428.07 ps | 107 | | range index | 212.19 ps | 212.69 ps | 108 | 109 | (In some other runs, the compile-time checked single index was _slightly faster_ than the runtime-checked single index.) 110 | 111 | ## Examples 112 | 113 | Here are some examples of how this library can be used. 114 | 115 | ### Example 1: getting an element and a slice from a fixed-size array 116 | ```rs 117 | use static_slicing::{StaticIndex, StaticRangeIndex}; 118 | 119 | fn main() { 120 | let x = [513, 947, 386, 1234]; 121 | 122 | // get the element at index 3 123 | let y = x[StaticIndex::<3>]; 124 | 125 | // get 2 elements starting from index 1 126 | let z: &[i32; 2] = &x[StaticRangeIndex::<1, 2>]; 127 | // this also works: 128 | let z: &[i32] = &x[StaticRangeIndex::<1, 2>]; 129 | 130 | // prints: y = 1234 131 | println!("y = {}", y); 132 | 133 | // prints: z = [947, 386] 134 | println!("z = {:?}", z); 135 | } 136 | ``` 137 | 138 | ### Example 2: mutating using static indexes 139 | ```rs 140 | use static_slicing::{StaticIndex, StaticRangeIndex}; 141 | 142 | fn main() { 143 | let mut x = [513, 947, 386, 1234]; 144 | 145 | assert_eq!(x[StaticIndex::<1>], 947); 146 | x[StaticIndex::<1>] = 1337; 147 | assert_eq!(x[StaticIndex::<1>], 1337); 148 | assert_eq!(x, [513, 1337, 386, 1234]); 149 | 150 | x[StaticRangeIndex::<2, 2>] = [7331, 4040]; 151 | 152 | assert_eq!(x[StaticRangeIndex::<2, 2>], [7331, 4040]); 153 | assert_eq!(x, [513, 1337, 7331, 4040]); 154 | } 155 | ``` 156 | 157 | ### Example 3: accidental out-of-bounds prevention 158 | ```rs 159 | use static_slicing::{StaticIndex, StaticRangeIndex}; 160 | 161 | fn main() { 162 | // read Background to understand why 163 | // `x` being an array reference is important! 164 | let x = &[513, 947, 386, 1234]; 165 | 166 | // this block compiles... 167 | { 168 | let y = x[5]; 169 | let z: &[i32; 2] = &x[2..5].try_into().unwrap(); 170 | } 171 | 172 | // ...but not this one! 173 | { 174 | let y = x[StaticIndex::<5>]; 175 | let z = x[StaticRangeIndex::<2, 3>]; 176 | } 177 | } 178 | ``` 179 | 180 | ### Example 4: reading from a `SliceWrapper` 181 | ```rs 182 | use static_slicing::{StaticIndex, StaticRangeIndex, SliceWrapper}; 183 | 184 | fn main() { 185 | let x = SliceWrapper::new(&[513, 947, 386, 1234][..]); 186 | 187 | { 188 | let y = x[StaticIndex::<3>]; 189 | let z = x[StaticRangeIndex::<2, 2>]; 190 | 191 | // prints: y = 1234 192 | println!("y = {}", y); 193 | 194 | // prints: z = [386, 1234] 195 | println!("z = {:?}", z); 196 | } 197 | 198 | { 199 | // both of these would panic at runtime 200 | // since they're performing invalid operations. 201 | let _ = x[StaticIndex::<5>]; 202 | let _ = x[StaticRangeIndex::<2, 4>]; 203 | } 204 | } 205 | ``` 206 | 207 | ### Example 5: writing to a `SliceWrapper` 208 | ```rs 209 | use static_slicing::{StaticIndex, StaticRangeIndex, SliceWrapper}; 210 | 211 | fn main() { 212 | // SliceWrappers are mutable under the following conditions: 213 | // 1. the wrapper itself has been declared as mutable, AND; 214 | // 2. the wrapper owns its wrapped data OR contains 215 | // a mutable reference to the data. 216 | let mut x = SliceWrapper::new(vec![513, 947, 386, 1234]); 217 | 218 | assert_eq!(x[StaticIndex::<3>], 1234); 219 | x[StaticIndex::<3>] = 1337; 220 | assert_eq!(x[StaticIndex::<3>], 1337); 221 | 222 | assert_eq!(x[StaticRangeIndex::<1, 2>], [947, 386]); 223 | x[StaticRangeIndex::<1, 2>] = [5555, 6666]; 224 | assert_eq!(x[StaticRangeIndex::<1, 2>], [5555, 6666]); 225 | } 226 | ``` 227 | 228 | ## Limitations 229 | 230 | There are a few limitations to be aware of: 231 | - `rust-analyzer` does not seem to be able to show the errors generated by the library's use of const panics. 232 | - `SliceWrapper` cannot perform compile-time checks under any circumstances, even if given an array reference with known length. This _may_ be blocked until specialization is stabilized. 233 | - The very existence of `SliceWrapper` is a limitation (in my mind, at least), but it's unlikely to go away unless radical changes are made to the orphan rules and coherence enforcement. (The fundamental problem is that I can't implement `Index(Mut)` for both `[T; N]` and `[T]`.) 234 | 235 | Otherwise, this should be a pretty easy library to deal with - it certainly made things a lot easier for my teammates and I! -------------------------------------------------------------------------------- /benches/static_index_benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use static_slicing::*; 3 | 4 | fn potentially_panicking_index(d: &[u8; 8]) -> u8 { 5 | d[4] 6 | } 7 | 8 | fn compile_checked_index(d: &[u8; 8]) -> u8 { 9 | d[StaticIndex::<4>] 10 | } 11 | 12 | fn criterion_benchmark(c: &mut Criterion) { 13 | c.bench_function("runtime checked single index", |b| b.iter(|| potentially_panicking_index(&black_box([5u8; 8])))); 14 | c.bench_function("compile-time checked single index", |b| b.iter(|| compile_checked_index(&black_box([5u8; 8])))); 15 | } 16 | 17 | criterion_group!(benches, criterion_benchmark); 18 | criterion_main!(benches); -------------------------------------------------------------------------------- /benches/static_range_index_benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | use static_slicing::*; 3 | 4 | fn potentially_panicking_index<'a>(d: &'a [u8; 8]) -> &'a [u8; 4] { 5 | let tmp = &d[4..8]; 6 | tmp.try_into().unwrap() 7 | } 8 | 9 | fn compile_checked_index<'a>(d: &'a [u8; 8]) -> &'a [u8; 4] { 10 | &d[StaticRangeIndex::<4, 4>] 11 | } 12 | 13 | fn criterion_benchmark(c: &mut Criterion) { 14 | c.bench_function("runtime checked range index", |b| b.iter(|| { 15 | potentially_panicking_index(&black_box([5u8; 8])); 16 | })); 17 | c.bench_function("compile-time checked range index", |b| b.iter(|| { 18 | compile_checked_index(&black_box([5u8; 8])); 19 | })); 20 | } 21 | 22 | criterion_group!(benches, criterion_benchmark); 23 | criterion_main!(benches); -------------------------------------------------------------------------------- /src/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 | //! ``` 82 | use 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. 91 | trait IsValidIndex { 92 | const RESULT: (); 93 | } 94 | 95 | /// A single-element index that exists entirely at compile time. 96 | pub struct StaticIndex; 97 | 98 | impl IsValidIndex for [T; N] { 99 | const RESULT: () = assert!(N > INDEX, "Index is out of bounds!"); 100 | } 101 | 102 | impl Index> for [T; N] { 103 | type Output = T; 104 | 105 | fn index(&self, _: StaticIndex) -> &Self::Output { 106 | let _ = <[T; N] as IsValidIndex>::RESULT; 107 | 108 | // SAFETY: We've verified bounds at compile time. 109 | unsafe { &*(self.as_ptr().add(INDEX) as *const T) } 110 | } 111 | } 112 | 113 | impl IndexMut> for [T; N] { 114 | fn index_mut(&mut self, _: StaticIndex) -> &mut Self::Output { 115 | let _ = <[T; N] as IsValidIndex>::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. 126 | trait IsValidRangeIndex { 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)`. 133 | pub struct StaticRangeIndex; 134 | 135 | impl IsValidRangeIndex 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 | 144 | impl 145 | Index> for [T; N] 146 | { 147 | type Output = [T; LENGTH]; 148 | 149 | fn index(&self, _: StaticRangeIndex) -> &Self::Output { 150 | let _ = <[T; N] as IsValidRangeIndex>::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 | 157 | impl 158 | IndexMut> for [T; N] 159 | { 160 | fn index_mut(&mut self, _: StaticRangeIndex) -> &mut Self::Output { 161 | let _ = <[T; N] as IsValidRangeIndex>::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)] 179 | pub 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 192 | ); 193 | 194 | impl<'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 | 200 | impl<'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 | 207 | impl<'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 | 213 | impl, const START: usize, const LENGTH: usize> Index> for SliceWrapper<'_, I, S> { 214 | type Output = [I; LENGTH]; 215 | 216 | fn index(&self, _: StaticRangeIndex) -> &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 | 227 | impl, const INDEX: usize> Index> for SliceWrapper<'_, I, S> { 228 | type Output = I; 229 | 230 | fn index(&self, _: StaticIndex) -> &Self::Output { 231 | self.0.as_ref().index(INDEX) 232 | } 233 | } 234 | 235 | impl + AsMut<[I]>, const START: usize, const LENGTH: usize> IndexMut> for SliceWrapper<'_, I, S> { 236 | fn index_mut(&mut self, _: StaticRangeIndex) -> &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 | 247 | impl + AsMut<[I]>, const INDEX: usize> IndexMut> for SliceWrapper<'_, I, S> { 248 | fn index_mut(&mut self, _: StaticIndex) -> &mut Self::Output { 249 | self.0.as_mut().index_mut(INDEX) 250 | } 251 | } 252 | 253 | #[cfg(test)] 254 | mod 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 | } --------------------------------------------------------------------------------