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> {}