index_ext/
lib.rs

1//! Adds more index types, improving correctness and clarifying intent.
2//!
3//! The crate is separated by modules which each roughly explore one particular index-related idea.
4//! As an overview, the main modules are:
5//!
6//! - In the [`mod@array`] module, a solution for repetitive try-into-unwrapping in the process of
7//!   interpreting slices as fixed-width arrays is presented.
8//! - In the [`int`] module, we explore numerical types with fallible conversion to indices on use,
9//!   interpreting a failure as out-of-bounds.
10//! - In the [`mem`] module, the efficient representation for the type intersection between
11//!   integers and pointer sized indices is presented as a set of concrete types.
12//! - In the [`tag`] module, we apply fancy type-level mechanisms to move the bounds check inherent
13//!   in safe slice accesses from the usage site to the construction of the index. See in
14//!   particular the Huffman example that demonstrates the optimization potential.
15//!
16//! Read the [`readme`] for some examples. In short:
17//!
18//! ```
19//! use index_ext::{Intex, SliceIntExt};
20//!
21//! let fine = [0u8; 2][Intex(1u32)];
22//! let also = [0u8; 2][Intex(1u128)];
23//!
24//! assert_eq!([0u8; 2].get_int(u128::max_value()), None);
25//! ```
26//!
27//! ## Unfinished features
28//!
29//! The marker WIP means it is worked on, Planned that it might be worked on due to intrigue of the
30//! author, and Idea itself is still unevaluated (looking for a usecase, for instance).
31//!
32//! `Planned`: An index type `CharAt(n: usize)` that dereferences to the characters of a string
33//! _around_ a particular position, represented by a string wrapper that allows converting into a
34//! `char`. In contrast to the standard operator, this would only panic for out-of-bounds
35//! coordinates and thus match the main expectation with regards to `len`.
36//!
37//! `Idea`: Note that a generic `PrefixChars<N: usize>`, retrieving the first N characters, would
38//! not be constant time which may be surprising if used in index position.
39//!
40//! `Idea`: An index type `InsertWith` for `HashMap` and `BTreeMap` that will construct an
41//! element when an entry is missing, similar to C++, and thus be a panic free alternative. _Maybe_
42//! we could index a `Vec<_>` with this type as well, extending as necessary, but this would again
43//! not be constant time. The problem here is the super trait relationship `IndexMut: Index` which
44//! might lead to many potential misuses. Also the common alternative of `entry.or_insert` is both
45//! simple an robust already.
46//!
47//! `Idea`: An adapter `OrEmpty` that uses `get` internally and substitutes an empty slice instead
48//! of panicking. Now this is maybe both clear and cute, I'd actually see some use here. It's
49//! unclear for which types to provide it and if there's a SemVer risk.
50//!
51//! ## Design notes
52//!
53//! The extension traits offered here have a slight ergonomic problem compared to being included in
54//! the standard library. Its `ops::Index` impls on slices are provided by the `SliceIndex` trait.
55//! Since this is a nightly trait _and_ sealed by design we can not use it. However, we also can
56//! not use a generic impl for all `T: crate::SliceIndex<[U]>` as this is forbidden by coherence
57//! rules for foreign types. We thus utilize two kinds of indexing: Implement the Index trait
58//! directly for all concrete applicable types and provide a single newtype which acts as a proxy
59//! for the otherwise unconstrained type parameter of the generic impl. If the types were added to
60//! `core` then this indirection would not be necessary and ergonomics would improve.
61#![no_std]
62#![deny(clippy::missing_safety_doc)]
63#![deny(missing_docs)]
64#![deny(unsafe_op_in_unsafe_fn)]
65
66#[cfg(feature = "alloc")]
67extern crate alloc;
68
69pub mod array;
70pub mod int;
71pub mod mem;
72pub mod tag;
73
74pub use array::ArrayPrefix;
75pub use int::SliceIntExt;
76
77/// Convert an arbitrary integer into an index.
78///
79/// This method simply constructs an inner transparent wrapper struct `Intex` but can be used as an
80/// alternative which is imported with the same name, and at the same time, as the trait.
81#[allow(non_snake_case)]
82pub fn Intex<T>(idx: T) -> int::Intex<T> {
83    int::Intex(idx)
84}
85
86#[cfg(doc)]
87macro_rules! doctest_readme {
88    { $content:expr } => {
89        /// A rendered version of the Readme file, documentation purpose only.
90        ///
91        #[doc = $content] pub mod readme {}
92    }
93}
94
95#[cfg(doc)]
96doctest_readme!(include_str!("../Readme.md"));
97
98#[cfg(test)]
99mod test {
100    use super::{Intex, SliceIntExt};
101
102    #[test]
103    #[should_panic = "100"]
104    fn panics_with_length_u32() {
105        [0u8; 0][Intex(100u32)];
106    }
107
108    #[test]
109    #[should_panic = "100"]
110    fn panics_with_length_u8() {
111        [0u8; 0][Intex(100u8)];
112    }
113
114    #[test]
115    #[should_panic = "-1"]
116    fn panics_with_length_i8() {
117        [0u8; 0][Intex(-1i8)];
118    }
119
120    #[test]
121    #[should_panic = "100000000000000000000000000000000000000"]
122    fn panics_with_length_u128() {
123        [0u8; 0][Intex(100_000_000_000_000_000_000_000_000_000_000_000_000u128)];
124    }
125
126    #[test]
127    fn index_with_all() {
128        let slice = [0u8; 10];
129        macro_rules! assert_slice_success {
130            (@$slice:path, $exp:expr) => {
131                assert!($slice.get_int($exp).is_some());
132            };
133            ($slice:path: $($exp:expr),*) => {
134                $(assert_slice_success!(@$slice, $exp);)*
135            }
136        }
137
138        macro_rules! assert_slice_fail {
139            (@$slice:path, $exp:expr) => {
140                assert_eq!($slice.get_int($exp), None);
141            };
142            ($slice:path: $($exp:expr),*) => {
143                $(assert_slice_fail!(@$slice, $exp);)*
144            }
145        }
146
147        assert_slice_success!(slice: 0u8, 0i8, 0u16, 0i16, 0u32, 0i32, 0u64, 0i64);
148        assert_slice_success!(slice: ..10u8, ..10i8, ..10u16, ..10i16, ..10u32, ..10i32, ..10u64, ..10i64);
149        assert_slice_success!(slice: 0..10u8, 0..10i8, 0..10u16, 0..10i16, 0..10u32, 0..10i32, 0..10u64, 0..10i64);
150        assert_slice_success!(slice: 10u8.., 10i8.., 10u16.., 10i16.., 10u32.., 10i32.., 10u64.., 10i64..);
151
152        assert_slice_fail!(slice: -1i8, -1i16, -1i32, -1i64);
153    }
154
155    #[test]
156    fn unchecked() {
157        let mut slice = [0u8, 1, 2, 3];
158        macro_rules! assert_slice_eq {
159            (@$slice:path, $idx:expr, $exp:expr) => {
160                assert_eq!($slice[Intex($idx)], $exp);
161                assert_eq!(&mut $slice[Intex($idx)], $exp);
162
163                unsafe {
164                    assert_eq!($slice.get_int_unchecked($idx), $exp);
165                    assert_eq!($slice.get_int_unchecked_mut($idx), $exp);
166                }
167            };
168            ($slice:path[idx], $result:expr, for idx in [$($idx:expr),*]) => {
169                $(assert_slice_eq!(@$slice, $idx, $result);)*
170            }
171        }
172
173        assert_slice_eq!(slice[idx], [1, 2],
174            for idx in [1u8..3, 1i8..3, 1u16..3, 1i16..3, 1u32..3, 1i32..3, 1u64..3, 1i64..3]);
175    }
176
177    #[test]
178    fn str_indices() {
179        let text = "What if ascii still has it?";
180        assert_eq!(text.get_int(8u8..13), Some("ascii"));
181    }
182}