size_hinter/hint_size.rs
1use core::{iter::FusedIterator, ops::Not};
2
3use fluent_result::bool::Then;
4
5use crate::InvalidSizeHint;
6use crate::size_hint::SizeHint;
7
8#[cfg(doc)]
9use crate::*;
10
11/// An [`Iterator`] adaptor that provides a custom [`Iterator::size_hint`] implementation.
12///
13/// This is most useful for providing a specific [`Iterator::size_hint`] implementation or hiding
14/// an underlying one for testing. In some cases using this adaptor may allow for optimization,
15/// though if an exact length is known it is recommended to use [`ExactLen`] instead. Using this
16/// adaptor to wrap an iterator that implements [`ExactSizeIterator`] or `TrustedLen` may also
17/// prevent optimizations.
18///
19/// Note this type is readonly. The field values may be read, but not modified.
20///
21/// # Fused iterator requirement
22///
23/// [`HintSize`]s with an bounded size hint (those created by [`HintSize::new`] or [`HintSize::try_new`])
24/// are required to wrap a [`FusedIterator`], because after the iterator completes (returns [`None`]),
25/// this adaptor could no longer guarantee a correct bound upper value if iteration were to resume.
26///
27/// Consider using an unbounded wrapper ([`HintSize::hide`] or [`HintSize::min`]) if the iterator is
28/// unfused.
29///
30/// # Safety
31///
32/// `HintSize` is always safe to use - it will never cause undefined behavior or memory unsafety,
33/// regardless of the hint values provided.
34///
35/// # Validity
36///
37/// Validation during construction ensures that adaptor does not contradict the wrapped iterator's
38/// guarantees. This is, the adaptor cannot produce a size hint that claims a upper bound less than
39/// the wrapped iterator's lower bound or a lower bound greater than the wrapped iterator's upper
40/// bound. If necessary this validation can be bypassed by wrapping in first [`HintSize::hide`] and
41/// then in the desired adaptor.
42///
43/// Regardless, it is still the caller's responsibility to ensure that the size hints accurately
44/// represent the number of elements remaining in the iterator. Incorrect size hints may cause
45/// incorrect behavior or panics in code that relies on these values.
46///
47/// If constructed with values valid for the wrapped iterator, the returned size hint will always
48/// be valid. However, if the iterator is fused, the bounds of a bounded `HintSize` are not
49/// guaranteed to converge at zero when iteration completes, and may change if a completed iterator
50/// is polled again.
51///
52/// # Examples
53///
54/// Hiding an iterator's size hint for testing.
55///
56/// ```rust
57/// # use size_hinter::{HintSize, SizeHint};
58/// let mut hidden = HintSize::hide(1..5);
59///
60/// assert_eq!(hidden.size_hint(), SizeHint::UNIVERSAL, "Initial size hint is universal");
61/// assert_eq!(hidden.next(), Some(1), "Underlying iterator is not changed");
62/// assert_eq!(hidden.size_hint(), SizeHint::UNIVERSAL, "Size hint remains universal");
63/// ```
64///
65/// Providing a specific size hint.
66///
67/// ```rust
68/// # use size_hinter::HintSize;
69/// let mut iter = HintSize::new(1..5, 3, 6);
70///
71/// assert_eq!(iter.size_hint(), (3, Some(6)), "should match the provided size hint");
72/// assert_eq!(iter.next(), Some(1), "Underlying iterator is not changed");
73/// assert_eq!(iter.next_back(), Some(4), "Underlying iterator is not changed");
74/// assert_eq!(iter.size_hint(), (1, Some(4)), "should reflect the new state");
75/// ```
76#[derive(Debug, Default, Clone)]
77#[readonly::make]
78pub struct HintSize<I: Iterator> {
79 /// The underlying iterator.
80 pub iterator: I,
81 /// The current size hint.
82 pub hint: SizeHint,
83}
84
85impl<I: Iterator> HintSize<I> {
86 /// Internal monomorphized failable constructor. Creates a [`HintSize`] with the provided `hint`.
87 ///
88 /// # Errors
89 ///
90 /// Returns [`InvalidSizeHint`] if the hint does not overlap with the `iterator`'s size hint.
91 ///
92 /// # Panics
93 ///
94 /// Panics if `iterator`'s [`Iterator::size_hint`] is invalid
95 #[inline]
96 #[track_caller]
97 fn try_new_impl(iterator: I, hint: SizeHint) -> Result<Self, InvalidSizeHint> {
98 let wrapped: SizeHint = iterator.size_hint().try_into().expect("iterator's size hint should be valid");
99 SizeHint::overlaps(hint, wrapped).not().then_err(InvalidSizeHint)?;
100 Ok(Self { iterator, hint })
101 }
102
103 /// Wraps `iterator` in a new [`HintSize`] with an initial bounded size hint of
104 /// `(lower, Some(upper))`.
105 ///
106 /// # Panics
107 ///
108 /// Panics if:
109 /// - `iterator`'s [`Iterator::size_hint`] is invalid
110 /// - `lower > upper`
111 /// - `upper` is less than the wrapped iterator's lower bound
112 /// - `lower` is greater than the wrapped iterator's upper bound (if present)
113 ///
114 /// # Examples
115 ///
116 /// ```rust
117 /// # use size_hinter::HintSize;
118 /// let mut iter = HintSize::new(1..5, 2, 6);
119 /// assert_eq!(iter.size_hint(), (2, Some(6)), "should match the provided size hint");
120 /// ```
121 #[inline]
122 pub fn new<IntoIter>(iterator: IntoIter, lower: usize, upper: usize) -> Self
123 where
124 IntoIter: IntoIterator<IntoIter = I>,
125 I: FusedIterator,
126 {
127 Self::try_new(iterator, lower, upper).expect("Invalid size hint")
128 }
129
130 /// Tries to wrap `iterator` in a new [`HintSize`] with an initial bounded size hint of
131 /// `(lower, Some(upper))`.
132 ///
133 /// # Errors
134 ///
135 /// Returns an [`InvalidSizeHint`] if:
136 /// - `lower > upper`
137 /// - `upper` is less than the wrapped iterator's lower bound
138 /// - `lower` is greater than the wrapped iterator's upper bound (if present)
139 ///
140 /// # Panics
141 ///
142 /// Panics if `iterator`'s [`Iterator::size_hint`] is invalid
143 ///
144 /// # Examples
145 ///
146 /// ```rust
147 /// # use size_hinter::{HintSize, InvalidSizeHint};
148 /// # fn main() -> Result<(), InvalidSizeHint> {
149 /// let mut iter = HintSize::try_new(1..5, 2, 6)?;
150 /// assert_eq!(iter.size_hint(), (2, Some(6)), "Initial size hint");
151 ///
152 /// let err: InvalidSizeHint = HintSize::try_new(1..5, 6, 2).expect_err("lower bound is > upper bound");
153 /// let err: InvalidSizeHint = HintSize::try_new(1..5, 6, 10).expect_err("hint lower bound is > iterator's upper bound");
154 /// let err: InvalidSizeHint = HintSize::try_new(1..5, 1, 3).expect_err("hint upper bound is < iterator's lower bound");
155 /// # Ok(())
156 /// }
157 /// ```
158 #[inline]
159 pub fn try_new<II>(iterator: II, lower: usize, upper: usize) -> Result<Self, InvalidSizeHint>
160 where
161 II: IntoIterator<IntoIter = I>,
162 I: FusedIterator,
163 {
164 let hint = SizeHint::try_bounded(lower, upper)?;
165 Self::try_new_impl(iterator.into_iter(), hint)
166 }
167
168 /// Wraps `iterator` in a new [`HintSize`] with an unbounded size hint based on `lower`.
169 ///
170 /// # Panics
171 ///
172 /// Panics if:
173 /// - `iterator`'s [`Iterator::size_hint`] is invalid
174 /// - `lower` is greater than the wrapped iterator's upper bound (if present).
175 ///
176 /// # Examples
177 ///
178 /// ```rust
179 /// # use size_hinter::HintSize;
180 /// let mut iter = HintSize::min(1..5, 2);
181 /// assert_eq!(iter.size_hint(), (2, None), "Initial size hint reflects lower");
182 /// ```
183 #[inline]
184 pub fn min(iterator: impl IntoIterator<IntoIter = I>, lower: usize) -> Self {
185 Self::try_min(iterator, lower).expect("Invalid size hint")
186 }
187
188 /// Tries to wrap `iterator` in a new [`HintSize`] with an unbounded size hint based on `lower`.
189 ///
190 /// # Errors
191 ///
192 /// Returns an [`InvalidSizeHint`] if `lower` is greater than the wrapped iterator's upper
193 /// bound (if present).
194 ///
195 /// # Panics
196 ///
197 /// Panics if `iterator`'s [`Iterator::size_hint`] is invalid
198 ///
199 /// # Examples
200 ///
201 /// ```rust
202 /// # use size_hinter::{HintSize, InvalidSizeHint};
203 /// # fn main() -> Result<(), InvalidSizeHint> {
204 /// let iter = HintSize::try_min(1..5, 2)?;
205 /// assert_eq!(iter.size_hint(), (2, None), "Initial size hint reflects lower");
206 ///
207 /// let err: InvalidSizeHint = HintSize::try_min(1..5, 6).expect_err("lower bound is > wrapped iterator's upper bound");
208 /// # Ok(())
209 /// # }
210 /// ```
211 #[inline]
212 pub fn try_min(iterator: impl IntoIterator<IntoIter = I>, lower: usize) -> Result<Self, InvalidSizeHint> {
213 Self::try_new_impl(iterator.into_iter(), SizeHint::unbounded(lower))
214 }
215
216 /// Wraps `iterator` with a new [`Iterator::size_hint`] implementation with a universal size hint.
217 ///
218 /// This implementation, and the size hint it returns, is always correct, and never changes.
219 ///
220 /// # Examples
221 ///
222 /// ```rust
223 /// # use size_hinter::HintSize;
224 /// let mut iter = HintSize::hide(1..5);
225 ///
226 /// assert_eq!(iter.size_hint(), (0, None), "Initial size hint is universal");
227 /// assert_eq!(iter.next(), Some(1), "Underlying iterator is not changed");
228 /// assert_eq!(iter.size_hint(), (0, None), "Size hint remains universal");
229 /// ```
230 #[inline]
231 pub fn hide(iterator: impl IntoIterator<IntoIter = I>) -> Self {
232 Self { iterator: iterator.into_iter(), hint: SizeHint::UNIVERSAL }
233 }
234
235 /// Consumes the adaptor and returns the underlying iterator.
236 ///
237 /// # Examples
238 ///
239 /// ```rust
240 /// use size_hinter::HintSize;
241 ///
242 /// let iter: std::vec::IntoIter<i32> = vec![1, 2, 3].into_iter();
243 /// let hint_iter = HintSize::hide(iter);
244 /// let inner: std::vec::IntoIter<i32> = hint_iter.into_inner();
245 /// ```
246 #[inline]
247 pub fn into_inner(self) -> I {
248 self.iterator
249 }
250}
251
252impl<I: Iterator> Iterator for HintSize<I> {
253 type Item = I::Item;
254
255 #[inline]
256 fn next(&mut self) -> Option<Self::Item> {
257 self.hint = self.hint.decrement();
258 self.iterator.next()
259 }
260
261 #[inline]
262 fn size_hint(&self) -> (usize, Option<usize>) {
263 self.hint.into()
264 }
265}
266
267impl<I: DoubleEndedIterator> DoubleEndedIterator for HintSize<I> {
268 #[inline]
269 fn next_back(&mut self) -> Option<Self::Item> {
270 self.hint = self.hint.decrement();
271 self.iterator.next_back()
272 }
273}
274
275impl<I: Iterator + FusedIterator> FusedIterator for HintSize<I> {}