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}