ix_rs/lib.rs
1#![no_std]
2//! This crate provides a trait ([`Ix`]) for values that permit contiguous subranges.
3
4/// A trait for values that permit contiguous subranges.
5///
6/// Implementations that override the provided functions must ensure
7/// their custom implementations are equivalent to the provided ones.
8///
9/// Implementations must uphold the following invariants:
10/// 1. `ix.in_range(min, max)` if and only if `Ix::range(min, max).any(|x| x == ix)`
11/// 2. If `ix.in_range(min, max)`, then `Ix::range(min, max).nth(ix.index(min, max)) == Some(ix)`
12/// 3. `Ix::range(min, max).map(|x| x.index(min, max))` yields equal items to `0..Ix::range_size(min, max)`
13/// 4. `Ix::range(min, max).map(|x| x.index_checked(min, max))` ever yields [`None`] if and only if `Ix::range_size_checked(min, max).is_none()`
14/// 5. `Ix::range_size(min, max) == Ix::range(min, max).count()`
15/// 6. `Ix::range_size_checked(min, max).is_none()` if and only if `Ix::range(min, max).count()` overflows or panics
16///
17/// Note that, for these properties, if one side of the equality panics or overflows the equality can be considered to hold.
18///
19/// # Examples
20///
21/// ```
22/// # use ix_rs::Ix;
23/// for (ix, i) in Ix::range(-45i128, 483).zip(0..) {
24/// assert_eq!(ix.index(-45, 483), i);
25/// }
26/// ```
27/// ```
28/// # use ix_rs::Ix;
29/// assert!(!(-30289i16).in_range(-746, 15564));
30/// ```
31/// ```
32/// # use ix_rs::Ix;
33/// assert_eq!(
34/// 2410117514u32.in_range(2073922791, 3401563124),
35/// Ix::range(2073922791u32, 3401563124).any(|x| x == 2410117514)
36/// ); // Property 1
37/// ```
38/// ```
39/// # use ix_rs::Ix;
40/// assert!(20i32.in_range(17, 5432));
41/// assert_eq!(Ix::range(17i32, 5432).nth(20i32.index(17, 5432)).unwrap(), 20);
42/// // Property 2
43/// ```
44/// ```
45/// # use ix_rs::Ix;
46/// assert!(0.in_range(-31597i16, 16417));
47/// assert_eq!(Ix::range(-31597i16, 16417).nth(0.index(-31597i16, 16417)).unwrap(), 0);
48/// // Property 2
49/// ```
50/// ```
51/// # use ix_rs::Ix;
52/// assert!(
53/// Ix::range(-633i32, 151)
54/// .map(|x| x.index(-633, 151))
55/// .eq(0..Ix::range_size(-633, 151))
56/// ) // Property 3
57/// ```
58/// ```
59/// # use ix_rs::Ix;
60/// assert_eq!(Ix::range(8079u32, 1836091).count(), Ix::range_size(8079u32, 1836091))
61/// // Property 5
62/// ```
63pub trait Ix: PartialOrd + Sized {
64 /// An iterator over the elements in a range of the implementing type.
65 type Range: Iterator<Item = Self>;
66 /// Generate an iterator over a range starting from `min` and stopping at `max`.
67 /// The resulting iterator must produce `min` and `max` at some point, each.
68 ///
69 /// # Panics
70 ///
71 /// Should panic if `min` is greater than `max`.
72 fn range(min: Self, max: Self) -> Self::Range;
73 /// Get the position of a value inside a range.
74 ///
75 /// # Panics
76 ///
77 /// Should panic if `min` is greater than `max`.
78 ///
79 /// Should panic if the value is not in the range (as determined by [`in_range`]).
80 ///
81 /// Panics if the resulting index is not representable as a [`usize`] value.
82 /// The default implementation does this by unwrapping the return value of [`index_checked`].
83 ///
84 /// [`in_range`]: Ix::in_range
85 /// [`index_checked`]: Ix::index_checked
86 fn index(self, min: Self, max: Self) -> usize {
87 self.index_checked(min, max).expect("index too large")
88 }
89 /// Get the position of a value inside a range.
90 /// If this would overflow the range of [`usize`], returns [`None`].
91 /// Checked version of [`index`].
92 ///
93 /// # Panics
94 ///
95 /// Should panic if `min` is greater than `max`.
96 ///
97 /// Should panic if the value is not in the range (as determined by [`in_range`]).
98 ///
99 /// [`index`]: Ix::index
100 /// [`in_range`]: Ix::in_range
101 fn index_checked(self, min: Self, max: Self) -> Option<usize>;
102 /// Check if a given value is inside a range.
103 ///
104 /// # Panics
105 ///
106 /// Should panic if `min` is greater than `max`.
107 fn in_range(self, min: Self, max: Self) -> bool;
108 /// Get the length of a range.
109 ///
110 /// # Panics
111 ///
112 /// Should panic if `min` is greater than `max`.
113 ///
114 /// Panics if the resulting size is not representable as a [`usize`] value.
115 /// The default implementation does this by unwrapping the return value of [`range_size_checked`].
116 ///
117 /// [`range_size_checked`]: Ix::range_size_checked
118 fn range_size(min: Self, max: Self) -> usize {
119 Ix::range_size_checked(min, max).expect("range size too large")
120 }
121 /// Get the length of a range.
122 /// If this would overflow the range of [`usize`], returns [`None`].
123 /// Checked version of [`range_size`].
124 ///
125 /// # Panics
126 ///
127 /// Should panic if `min` is greater than `max`.
128 ///
129 /// [`range_size`]: Ix::range_size
130 fn range_size_checked(min: Self, max: Self) -> Option<usize>;
131}
132
133macro_rules! assert_ordered {
134 ($min: expr, $max: expr) => {
135 if $min > $max {
136 panic!("min is greater than max");
137 }
138 };
139}
140
141macro_rules! assert_in_range {
142 ($min: expr, $max: expr, $ix: expr) => {
143 if $ix < $min {
144 panic!("index is outside range (< min)");
145 } else if $ix > $max {
146 panic!("index is outside range (> max)");
147 }
148 };
149}
150
151macro_rules! impl_ix_numeric {
152 ($($t: ty),+) => {
153 $(
154 impl $crate::Ix for $t {
155 type Range = ::core::ops::RangeInclusive<$t>;
156 fn range(min: Self, max: Self) -> Self::Range {
157 assert_ordered!(min, max);
158 min..=max
159 }
160 fn index_checked(self, min: Self, max: Self) -> Option<usize> {
161 assert_ordered!(min, max);
162 assert_in_range!(min, max, self);
163 usize::try_from(self - min).ok()
164 }
165 fn in_range(self, min: Self, max: Self) -> bool {
166 assert_ordered!(min, max);
167 min <= self && self <= max
168 }
169 fn range_size_checked(min: Self, max: Self) -> Option<usize> {
170 assert_ordered!(min, max);
171 usize::try_from(max - min)
172 .ok()
173 .and_then(|n| n.checked_add(1))
174 }
175 }
176 )+
177 };
178}
179
180impl_ix_numeric!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, usize, isize);