Skip to main content

lender/adapters/
chunky.rs

1use core::ops::ControlFlow;
2
3use crate::{
4    Chunk, ExactSizeFallibleLender, ExactSizeLender, FallibleLender, FusedLender, Lend, Lender,
5    Lending, try_trait_v2::Try,
6};
7
8/// A lender yielding lenders ([`Chunk`]s) returning the next
9/// `chunk_size` lends.
10///
11/// This is the closest lending approximation to
12/// `core::iter::ArrayChunks` (unstable), as we cannot accumulate
13/// the lends into an array. Unlike `ArrayChunks`, which yields
14/// fixed-size arrays, `Chunky` yields [`Chunk`] lenders that
15/// must be consumed to access the elements.
16///
17/// This struct is created by [`Lender::chunky`] or
18/// [`FallibleLender::chunky`].
19///
20/// # Important: Partial Chunk Consumption
21///
22/// **Each [`Chunk`] yielded by `Chunky` must be fully consumed
23/// before requesting the next chunk.** If a chunk is not fully
24/// consumed, the unconsumed elements are effectively skipped,
25/// and the next chunk will start from whatever position the
26/// underlying lender is at.
27///
28/// This behavior differs from [`core::slice::Chunks`] where
29/// each chunk is a complete view. With `Chunky`, you are
30/// borrowing from a single underlying lender, so partial
31/// consumption affects subsequent chunks.
32///
33/// Partial chunk consumption also has the consequence of not
34/// enumerating entirely the elements returned by the underlying
35/// lender, as the number of chunks is computed at the start.
36/// Thus, in case of partial chunk consumption the last element
37/// of the last chunk will not be the last element of the underlying
38/// lender.
39#[derive(Debug, Clone)]
40#[must_use = "lenders are lazy and do nothing unless consumed"]
41pub struct Chunky<L> {
42    pub(crate) lender: L,
43    pub(crate) chunk_size: usize,
44    pub(crate) len: usize,
45}
46
47impl<L> Chunky<L>
48where
49    L: Lender + ExactSizeLender,
50{
51    #[inline]
52    pub(crate) fn new(lender: L, chunk_size: usize) -> Self {
53        let _ = L::__check_covariance(crate::CovariantProof::new());
54        assert!(chunk_size != 0, "chunk size must be non-zero");
55        let len = lender.len().div_ceil(chunk_size);
56        Self {
57            lender,
58            chunk_size,
59            len,
60        }
61    }
62}
63
64impl<L> Chunky<L>
65where
66    L: FallibleLender + ExactSizeFallibleLender,
67{
68    #[inline]
69    pub(crate) fn new_fallible(lender: L, chunk_size: usize) -> Self {
70        let _ = L::__check_covariance(crate::CovariantProof::new());
71        assert!(chunk_size != 0, "chunk size must be non-zero");
72        let len = lender.len().div_ceil(chunk_size);
73        Self {
74            lender,
75            chunk_size,
76            len,
77        }
78    }
79}
80
81impl<L> Chunky<L> {
82    /// Returns the inner lender.
83    #[inline(always)]
84    pub fn into_inner(self) -> L {
85        self.lender
86    }
87
88    /// Returns the inner lender and the chunk size.
89    #[inline(always)]
90    pub fn into_parts(self) -> (L, usize) {
91        (self.lender, self.chunk_size)
92    }
93}
94
95impl<'lend, L> Lending<'lend> for Chunky<L>
96where
97    L: Lender,
98{
99    type Lend = Chunk<'lend, L>;
100}
101
102impl<L> Lender for Chunky<L>
103where
104    L: Lender,
105{
106    // SAFETY: the lend is a Chunk wrapping L
107    crate::unsafe_assume_covariance!();
108    #[inline]
109    fn next(&mut self) -> Option<Lend<'_, Self>> {
110        if self.len > 0 {
111            self.len -= 1;
112            Some(self.lender.next_chunk(self.chunk_size))
113        } else {
114            None
115        }
116    }
117
118    #[inline]
119    fn nth(&mut self, n: usize) -> Option<Lend<'_, Self>> {
120        if n < self.len {
121            // Skip n chunks by advancing the inner lender
122            let skip = n
123                .checked_mul(self.chunk_size)
124                .expect("overflow in Chunky::nth");
125            self.len -= n;
126            if self.lender.advance_by(skip).is_err() {
127                unreachable!();
128            }
129            self.next()
130        } else {
131            // Exhaust
132            if self.len > 0 {
133                let skip = self
134                    .len
135                    .checked_mul(self.chunk_size)
136                    .expect("overflow in Chunky::nth");
137                let _ = self.lender.advance_by(skip);
138                self.len = 0;
139            }
140            None
141        }
142    }
143
144    #[inline(always)]
145    fn size_hint(&self) -> (usize, Option<usize>) {
146        (self.len, Some(self.len))
147    }
148
149    #[inline(always)]
150    fn count(self) -> usize {
151        self.len
152    }
153
154    #[inline]
155    fn try_fold<B, F, R>(&mut self, init: B, mut f: F) -> R
156    where
157        Self: Sized,
158        F: FnMut(B, Lend<'_, Self>) -> R,
159        R: Try<Output = B>,
160    {
161        let mut acc = init;
162        let sz = self.chunk_size;
163        while self.len > 0 {
164            self.len -= 1;
165            acc = match f(acc, self.lender.next_chunk(sz)).branch() {
166                ControlFlow::Break(x) => return R::from_residual(x),
167                ControlFlow::Continue(x) => x,
168            };
169        }
170        R::from_output(acc)
171    }
172
173    #[inline]
174    fn fold<B, F>(mut self, init: B, mut f: F) -> B
175    where
176        Self: Sized,
177        F: FnMut(B, Lend<'_, Self>) -> B,
178    {
179        let mut accum = init;
180        let sz = self.chunk_size;
181        while self.len > 0 {
182            self.len -= 1;
183            accum = f(accum, self.lender.next_chunk(sz));
184        }
185        accum
186    }
187}
188
189impl<L> FusedLender for Chunky<L> where L: FusedLender {}
190
191// Note: Chunky deliberately does not implement ExactSizeLender (nor
192// ExactSizeFallibleLender). The `len` field is pre-computed from the
193// underlying lender's length at construction time and counts *chunks*,
194// not elements. If a chunk is only partially consumed, the remaining
195// elements are silently skipped when the next chunk is requested, so
196// the pre-computed count may overestimate the number of lends that
197// `next()` will actually produce. This violates the ExactSizeLender
198// contract, which requires `len()` to match the actual remaining
199// count. The chunk count is still available through `size_hint()`.