miniextendr_api/altrep_data/iter/state.rs
1//! Core iterator-backed ALTREP data adaptors.
2//!
3//! Provides `IterState<I, T>` (the shared lazy-caching state machine) and
4//! data-adaptor types for the integer/real/logical/raw ALTREP families:
5//! `IterIntData`, `IterRealData`, `IterLogicalData`, `IterRawData`. The
6//! string/list/complex adaptors (`IterStringData`, `IterListData`,
7//! `IterComplexData`) live in `super::coerce`.
8//!
9//! See the [`super`](crate::altrep_data::iter) module docs for how to expose
10//! these adaptors to R: they implement only the data-level traits
11//! ([`AltrepLen`] + `Alt*Data`) and must be wrapped in a concrete
12//! `#[derive(Altrep*)]` + `#[altrep(manual)]` struct to back a live ALTREP
13//! vector.
14
15use std::cell::RefCell;
16use std::sync::OnceLock;
17
18use crate::altrep_data::{
19 AltIntegerData, AltLogicalData, AltRawData, AltRealData, AltrepLen, Logical, fill_region,
20};
21
22/// Core state for iterator-backed ALTREP vectors.
23///
24/// Provides lazy element generation with caching for random-access semantics.
25/// Iterator elements are cached as they're accessed, enabling repeatable reads.
26///
27/// # Type Parameters
28///
29/// - `I`: The iterator type (must be `ExactSizeIterator` or provide explicit length)
30/// - `T`: The element type produced by the iterator
31///
32/// # Design
33///
34/// - **Lazy:** Elements generated on-demand via `elt(i)`
35/// - **Cached:** Once generated, elements stored in cache for repeat access
36/// - **Materializable:** Can be fully materialized for `Dataptr` or serialization
37/// - **Safe:** Uses `RefCell` for interior mutability, protected by R's GC
38pub struct IterState<I, T> {
39 /// Vector length (from `ExactSizeIterator::len()` or explicit)
40 len: usize,
41 /// Iterator state (consumed as we advance)
42 iter: RefCell<Option<I>>,
43 /// Cache of generated elements (prefix of the vector)
44 cache: RefCell<Vec<T>>,
45 /// Full materialization (when all elements have been generated)
46 materialized: OnceLock<Vec<T>>,
47}
48
49impl<I, T> IterState<I, T>
50where
51 I: Iterator<Item = T>,
52{
53 /// Create a new iterator state with an explicit length.
54 ///
55 /// # Arguments
56 ///
57 /// - `iter`: The iterator to wrap
58 /// - `len`: The expected number of elements
59 ///
60 /// # Length Mismatch
61 ///
62 /// If the iterator produces a different number of elements than `len`:
63 /// - Fewer elements: Missing indices return `None`/NA/default values
64 /// - More elements: Extra elements are ignored (truncated to `len`)
65 ///
66 /// A warning is printed to stderr when a mismatch is detected.
67 pub fn new(iter: I, len: usize) -> Self {
68 Self {
69 len,
70 iter: RefCell::new(Some(iter)),
71 cache: RefCell::new(Vec::with_capacity(len.min(1024))),
72 materialized: OnceLock::new(),
73 }
74 }
75
76 /// Ensure the element at index `i` is in the cache and return it by value.
77 ///
78 /// Advances the iterator as needed. Only works for `Copy` types.
79 ///
80 /// # Returns
81 ///
82 /// - `Some(T)` if element exists and has been generated
83 /// - `None` if index is out of bounds or iterator exhausted before reaching index `i`
84 pub fn get_element(&self, i: usize) -> Option<T>
85 where
86 T: Copy,
87 {
88 // Check bounds
89 if i >= self.len {
90 return None;
91 }
92
93 // If fully materialized, return from materialized vec
94 if let Some(vec) = self.materialized.get() {
95 return vec.get(i).copied();
96 }
97
98 // Otherwise, check cache and advance iterator if needed
99 let mut cache = self.cache.borrow_mut();
100
101 // Already in cache?
102 if i < cache.len() {
103 return Some(cache[i]);
104 }
105
106 // Need to advance iterator to index i
107 let mut iter_opt = self.iter.borrow_mut();
108 {
109 let iter = iter_opt.as_mut()?;
110
111 // Fill cache up to and including index i
112 while cache.len() <= i {
113 if let Some(elem) = iter.next() {
114 cache.push(elem);
115 } else {
116 // Iterator exhausted before reaching expected length
117 return None;
118 }
119 }
120 }
121
122 let value = cache[i];
123
124 // If we've generated the full vector via random-access, promote the cache
125 // to the materialized storage so `as_slice()` can expose it.
126 if cache.len() == self.len {
127 iter_opt.take();
128
129 let vec = std::mem::take(&mut *cache);
130 drop(cache);
131 drop(iter_opt);
132
133 let _ = self.materialized.set(vec);
134 }
135
136 Some(value)
137 }
138
139 /// Materialize all remaining elements from the iterator.
140 ///
141 /// After this call, all elements are guaranteed to be in memory and
142 /// `as_materialized()` will return `Some`.
143 ///
144 /// # Length Mismatch Handling
145 ///
146 /// If the iterator produces fewer elements than declared `len`, the missing
147 /// elements are left uninitialized in the cache (callers should handle this
148 /// via bounds checking). If the iterator produces more elements than declared,
149 /// extra elements are silently ignored (truncated to `len`).
150 ///
151 /// A warning is printed to stderr if a length mismatch is detected.
152 pub fn materialize_all(&self) -> &[T] {
153 // Already materialized?
154 if let Some(vec) = self.materialized.get() {
155 return vec;
156 }
157
158 // Consume iterator and move cache to materialized storage
159 let mut cache = self.cache.borrow_mut();
160 let mut iter_opt = self.iter.borrow_mut();
161
162 if let Some(iter) = iter_opt.take() {
163 // Drain remaining elements (up to self.len to avoid memory issues)
164 for elem in iter {
165 if cache.len() >= self.len {
166 // Iterator produced more than expected - truncate and warn
167 eprintln!(
168 "[miniextendr warning] iterator ALTREP: iterator produced more elements than declared length ({}), truncating",
169 self.len
170 );
171 break;
172 }
173 cache.push(elem);
174 }
175
176 // Check if iterator exhausted early
177 if cache.len() < self.len {
178 eprintln!(
179 "[miniextendr warning] iterator ALTREP: iterator produced {} elements, expected {} - accessing missing indices will return NA/default",
180 cache.len(),
181 self.len
182 );
183 }
184 }
185
186 // Move cache to materialized (take ownership)
187 let vec = std::mem::take(&mut *cache);
188 drop(cache);
189 drop(iter_opt);
190
191 // Store in OnceLock and return reference
192 self.materialized.get_or_init(|| vec)
193 }
194
195 /// Get the materialized vector if all elements have been generated.
196 ///
197 /// Returns `None` if not yet fully materialized.
198 pub fn as_materialized(&self) -> Option<&[T]> {
199 self.materialized.get().map(|v| v.as_slice())
200 }
201
202 /// Get the current length.
203 pub fn len(&self) -> usize {
204 self.len
205 }
206
207 /// Check if the vector is empty.
208 pub fn is_empty(&self) -> bool {
209 self.len == 0
210 }
211}
212
213impl<I, T> IterState<I, T>
214where
215 I: ExactSizeIterator<Item = T>,
216{
217 /// Create a new iterator state from an `ExactSizeIterator`.
218 ///
219 /// The length is automatically determined from `iter.len()`.
220 pub fn from_exact_size(iter: I) -> Self {
221 let len = iter.len();
222 Self::new(iter, len)
223 }
224}
225
226/// Iterator-backed integer vector data adaptor.
227///
228/// Wraps an iterator producing `i32` values and implements the data-level
229/// traits ([`AltrepLen`] + [`AltIntegerData`]) for backing an ALTREP integer
230/// vector. To expose it to R, wrap it in a `#[derive(AltrepInteger)]` +
231/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
232///
233/// # Example
234///
235/// ```ignore
236/// use miniextendr_api::altrep_data::IterIntData;
237///
238/// // Create from an iterator
239/// let data = IterIntData::from_iter((1..=10).map(|x| x * 2), 10);
240/// ```
241pub struct IterIntData<I: Iterator<Item = i32>> {
242 state: IterState<I, i32>,
243}
244
245impl<I: Iterator<Item = i32>> IterIntData<I> {
246 /// Create from an iterator with explicit length.
247 pub fn from_iter(iter: I, len: usize) -> Self {
248 Self {
249 state: IterState::new(iter, len),
250 }
251 }
252}
253
254impl<I: ExactSizeIterator<Item = i32>> IterIntData<I> {
255 /// Create from an ExactSizeIterator (length auto-detected).
256 pub fn from_exact_iter(iter: I) -> Self {
257 Self {
258 state: IterState::from_exact_size(iter),
259 }
260 }
261}
262
263impl<I: Iterator<Item = i32>> AltrepLen for IterIntData<I> {
264 fn len(&self) -> usize {
265 self.state.len()
266 }
267}
268
269impl<I: Iterator<Item = i32>> AltIntegerData for IterIntData<I> {
270 fn elt(&self, i: usize) -> i32 {
271 self.state
272 .get_element(i)
273 .unwrap_or(crate::altrep_traits::NA_INTEGER)
274 }
275
276 fn as_slice(&self) -> Option<&[i32]> {
277 self.state.as_materialized()
278 }
279
280 fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
281 fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
282 }
283}
284
285/// Iterator-backed real (f64) vector data adaptor.
286///
287/// Wraps an iterator producing `f64` values and implements the data-level
288/// traits ([`AltrepLen`] + [`AltRealData`]) for backing an ALTREP real vector.
289/// To expose it to R, wrap it in a `#[derive(AltrepReal)]` +
290/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
291pub struct IterRealData<I: Iterator<Item = f64>> {
292 state: IterState<I, f64>,
293}
294
295impl<I: Iterator<Item = f64>> IterRealData<I> {
296 /// Create from an iterator with explicit length.
297 pub fn from_iter(iter: I, len: usize) -> Self {
298 Self {
299 state: IterState::new(iter, len),
300 }
301 }
302}
303
304impl<I: ExactSizeIterator<Item = f64>> IterRealData<I> {
305 /// Create from an ExactSizeIterator (length auto-detected).
306 pub fn from_exact_iter(iter: I) -> Self {
307 Self {
308 state: IterState::from_exact_size(iter),
309 }
310 }
311}
312
313impl<I: Iterator<Item = f64>> AltrepLen for IterRealData<I> {
314 fn len(&self) -> usize {
315 self.state.len()
316 }
317}
318
319impl<I: Iterator<Item = f64>> AltRealData for IterRealData<I> {
320 fn elt(&self, i: usize) -> f64 {
321 self.state.get_element(i).unwrap_or(f64::NAN)
322 }
323
324 fn as_slice(&self) -> Option<&[f64]> {
325 self.state.as_materialized()
326 }
327
328 fn get_region(&self, start: usize, len: usize, buf: &mut [f64]) -> usize {
329 fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
330 }
331}
332
333/// Iterator-backed logical vector data adaptor.
334///
335/// Wraps an iterator producing `bool` values and implements the data-level
336/// traits ([`AltrepLen`] + [`AltLogicalData`]) for backing an ALTREP logical
337/// vector. To expose it to R, wrap it in a `#[derive(AltrepLogical)]` +
338/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
339pub struct IterLogicalData<I: Iterator<Item = bool>> {
340 state: IterState<I, bool>,
341}
342
343impl<I: Iterator<Item = bool>> IterLogicalData<I> {
344 /// Create from an iterator with explicit length.
345 pub fn from_iter(iter: I, len: usize) -> Self {
346 Self {
347 state: IterState::new(iter, len),
348 }
349 }
350}
351
352impl<I: ExactSizeIterator<Item = bool>> IterLogicalData<I> {
353 /// Create from an ExactSizeIterator (length auto-detected).
354 pub fn from_exact_iter(iter: I) -> Self {
355 Self {
356 state: IterState::from_exact_size(iter),
357 }
358 }
359}
360
361impl<I: Iterator<Item = bool>> AltrepLen for IterLogicalData<I> {
362 fn len(&self) -> usize {
363 self.state.len()
364 }
365}
366
367impl<I: Iterator<Item = bool>> AltLogicalData for IterLogicalData<I> {
368 fn elt(&self, i: usize) -> Logical {
369 self.state
370 .get_element(i)
371 .map(Logical::from_bool)
372 .unwrap_or(Logical::Na)
373 }
374
375 fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
376 fill_region(start, len, self.len(), buf, |idx| self.elt(idx).to_r_int())
377 }
378}
379
380/// Iterator-backed raw (u8) vector data adaptor.
381///
382/// Wraps an iterator producing `u8` values and implements the data-level
383/// traits ([`AltrepLen`] + [`AltRawData`]) for backing an ALTREP raw vector.
384/// To expose it to R, wrap it in a `#[derive(AltrepRaw)]` +
385/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
386pub struct IterRawData<I: Iterator<Item = u8>> {
387 state: IterState<I, u8>,
388}
389
390impl<I: Iterator<Item = u8>> IterRawData<I> {
391 /// Create from an iterator with explicit length.
392 pub fn from_iter(iter: I, len: usize) -> Self {
393 Self {
394 state: IterState::new(iter, len),
395 }
396 }
397}
398
399impl<I: ExactSizeIterator<Item = u8>> IterRawData<I> {
400 /// Create from an ExactSizeIterator (length auto-detected).
401 pub fn from_exact_iter(iter: I) -> Self {
402 Self {
403 state: IterState::from_exact_size(iter),
404 }
405 }
406}
407
408impl<I: Iterator<Item = u8>> AltrepLen for IterRawData<I> {
409 fn len(&self) -> usize {
410 self.state.len()
411 }
412}
413
414impl<I: Iterator<Item = u8>> AltRawData for IterRawData<I> {
415 fn elt(&self, i: usize) -> u8 {
416 self.state.get_element(i).unwrap_or(0)
417 }
418
419 fn as_slice(&self) -> Option<&[u8]> {
420 self.state.as_materialized()
421 }
422
423 fn get_region(&self, start: usize, len: usize, buf: &mut [u8]) -> usize {
424 fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
425 }
426}