Skip to main content

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