Skip to main content

miniextendr_api/altrep_data/iter/
coerce.rs

1//! Iterator-backed ALTREP data adaptors with coercion support.
2//!
3//! Provides `IterIntCoerceData`, `IterRealCoerceData`, and `IterIntFromBoolData`
4//! for iterators that produce values coercible to the target R type, plus the
5//! string/list/complex adaptors (`IterStringData`, `IterListData`,
6//! `IterComplexData`).
7//!
8//! See the [`super`](crate::altrep_data::iter) module docs for how to expose
9//! these adaptors to R (wrap in a `#[derive(Altrep*)]` + `#[altrep(manual)]`
10//! struct).
11
12use super::IterState;
13use crate::SEXP;
14use crate::altrep_data::{
15    AltComplexData, AltIntegerData, AltListData, AltRealData, AltStringData, AltrepLen, fill_region,
16};
17
18/// Iterator-backed integer vector data adaptor with coercion from any integer-like type.
19///
20/// Wraps an iterator producing values that coerce to `i32` (e.g., `u16`, `i8`, etc.)
21/// and implements the data-level traits ([`AltrepLen`] + [`AltIntegerData`]).
22/// To expose it to R, wrap it in a `#[derive(AltrepInteger)]` +
23/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
24///
25/// # Example
26///
27/// ```ignore
28/// use miniextendr_api::altrep_data::IterIntCoerceData;
29///
30/// // Create from an iterator of u16 values
31/// let iter = (0..10u16).map(|x| x * 100);
32/// let data = IterIntCoerceData::from_iter(iter, 10);
33/// // Values are coerced from u16 to i32 when accessed
34/// ```
35pub struct IterIntCoerceData<I, T>
36where
37    I: Iterator<Item = T>,
38    T: crate::coerce::Coerce<i32> + Copy,
39{
40    state: IterState<I, T>,
41}
42
43impl<I, T> IterIntCoerceData<I, T>
44where
45    I: Iterator<Item = T>,
46    T: crate::coerce::Coerce<i32> + Copy,
47{
48    /// Create from an iterator with explicit length.
49    pub fn from_iter(iter: I, len: usize) -> Self {
50        Self {
51            state: IterState::new(iter, len),
52        }
53    }
54}
55
56impl<I, T> IterIntCoerceData<I, T>
57where
58    I: ExactSizeIterator<Item = T>,
59    T: crate::coerce::Coerce<i32> + Copy,
60{
61    /// Create from an ExactSizeIterator (length auto-detected).
62    pub fn from_exact_iter(iter: I) -> Self {
63        Self {
64            state: IterState::from_exact_size(iter),
65        }
66    }
67}
68
69impl<I, T> AltrepLen for IterIntCoerceData<I, T>
70where
71    I: Iterator<Item = T>,
72    T: crate::coerce::Coerce<i32> + Copy,
73{
74    fn len(&self) -> usize {
75        self.state.len()
76    }
77}
78
79impl<I, T> AltIntegerData for IterIntCoerceData<I, T>
80where
81    I: Iterator<Item = T>,
82    T: crate::coerce::Coerce<i32> + Copy,
83{
84    fn elt(&self, i: usize) -> i32 {
85        self.state
86            .get_element(i)
87            .map(|val| val.coerce())
88            .unwrap_or(crate::altrep_traits::NA_INTEGER)
89    }
90
91    fn as_slice(&self) -> Option<&[i32]> {
92        // Can't return slice of i32 when cached values are type T
93        // Would need a separate coerced cache
94        None
95    }
96
97    fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
98        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
99    }
100}
101
102/// Iterator-backed real vector data adaptor with coercion from any float-like type.
103///
104/// Wraps an iterator producing values that coerce to `f64` (e.g., `f32`, integer types).
105///
106/// # Example
107///
108/// ```ignore
109/// use miniextendr_api::altrep_data::IterRealCoerceData;
110///
111/// // Create from an iterator of f32 values
112/// let iter = (0..5).map(|x| x as f32 * 1.5);
113/// let data = IterRealCoerceData::from_iter(iter, 5);
114/// // Values are coerced from f32 to f64 when accessed
115/// ```
116pub struct IterRealCoerceData<I, T>
117where
118    I: Iterator<Item = T>,
119    T: crate::coerce::Coerce<f64> + Copy,
120{
121    state: IterState<I, T>,
122}
123
124impl<I, T> IterRealCoerceData<I, T>
125where
126    I: Iterator<Item = T>,
127    T: crate::coerce::Coerce<f64> + Copy,
128{
129    /// Create from an iterator with explicit length.
130    pub fn from_iter(iter: I, len: usize) -> Self {
131        Self {
132            state: IterState::new(iter, len),
133        }
134    }
135}
136
137impl<I, T> IterRealCoerceData<I, T>
138where
139    I: ExactSizeIterator<Item = T>,
140    T: crate::coerce::Coerce<f64> + Copy,
141{
142    /// Create from an ExactSizeIterator (length auto-detected).
143    pub fn from_exact_iter(iter: I) -> Self {
144        Self {
145            state: IterState::from_exact_size(iter),
146        }
147    }
148}
149
150impl<I, T> AltrepLen for IterRealCoerceData<I, T>
151where
152    I: Iterator<Item = T>,
153    T: crate::coerce::Coerce<f64> + Copy,
154{
155    fn len(&self) -> usize {
156        self.state.len()
157    }
158}
159
160impl<I, T> AltRealData for IterRealCoerceData<I, T>
161where
162    I: Iterator<Item = T>,
163    T: crate::coerce::Coerce<f64> + Copy,
164{
165    fn elt(&self, i: usize) -> f64 {
166        self.state
167            .get_element(i)
168            .map(|val| val.coerce())
169            .unwrap_or(f64::NAN)
170    }
171
172    fn as_slice(&self) -> Option<&[f64]> {
173        // Can't return slice of f64 when cached values are type T
174        None
175    }
176
177    fn get_region(&self, start: usize, len: usize, buf: &mut [f64]) -> usize {
178        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
179    }
180}
181
182/// Iterator-backed integer vector data adaptor with coercion from bool.
183///
184/// Wraps an iterator producing `bool` values that coerce to `i32`.
185/// Useful for converting boolean iterators to integer vectors.
186pub struct IterIntFromBoolData<I>
187where
188    I: Iterator<Item = bool>,
189{
190    state: IterState<I, bool>,
191}
192
193impl<I> IterIntFromBoolData<I>
194where
195    I: Iterator<Item = bool>,
196{
197    /// Create from an iterator with explicit length.
198    pub fn from_iter(iter: I, len: usize) -> Self {
199        Self {
200            state: IterState::new(iter, len),
201        }
202    }
203}
204
205impl<I> IterIntFromBoolData<I>
206where
207    I: ExactSizeIterator<Item = bool>,
208{
209    /// Create from an ExactSizeIterator (length auto-detected).
210    pub fn from_exact_iter(iter: I) -> Self {
211        Self {
212            state: IterState::from_exact_size(iter),
213        }
214    }
215}
216
217impl<I> AltrepLen for IterIntFromBoolData<I>
218where
219    I: Iterator<Item = bool>,
220{
221    fn len(&self) -> usize {
222        self.state.len()
223    }
224}
225
226impl<I> AltIntegerData for IterIntFromBoolData<I>
227where
228    I: Iterator<Item = bool>,
229{
230    fn elt(&self, i: usize) -> i32 {
231        use crate::coerce::Coerce;
232        self.state
233            .get_element(i)
234            .map(|val| val.coerce())
235            .unwrap_or(crate::altrep_traits::NA_INTEGER)
236    }
237
238    fn as_slice(&self) -> Option<&[i32]> {
239        None
240    }
241
242    fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
243        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
244    }
245}
246
247/// Iterator-backed string vector data adaptor.
248///
249/// Wraps an iterator producing `String` values and implements the data-level
250/// traits ([`AltrepLen`] + [`AltStringData`]) for backing an ALTREP character
251/// vector. To expose it to R, wrap it in a `#[derive(AltrepString)]` +
252/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
253///
254/// # Performance Warning
255///
256/// Unlike other `Iter*Data` types, **accessing ANY element forces full materialization
257/// of the entire iterator**. This is because R's `AltStringData::elt()` returns a borrowed
258/// `&str`, which requires stable storage. The internal `RefCell` cannot provide the required
259/// lifetime, so all strings must be materialized upfront.
260///
261/// This means:
262/// - `elt(0)` on a million-element iterator will generate ALL million strings
263/// - There is no lazy evaluation benefit for string iterators
264/// - Memory usage equals the full vector regardless of access patterns
265///
266/// For truly lazy string ALTREP, consider implementing a custom type that stores
267/// strings in a way that allows borrowing without full materialization (e.g., arena
268/// allocation or caching generated strings incrementally).
269///
270/// # Example
271///
272/// ```ignore
273/// use miniextendr_api::altrep_data::IterStringData;
274///
275/// let iter = (0..5).map(|x| format!("item_{}", x));
276/// let data = IterStringData::from_iter(iter, 5);
277/// // First access to ANY element will materialize all 5 strings
278/// ```
279pub struct IterStringData<I>
280where
281    I: Iterator<Item = String>,
282{
283    state: IterState<I, String>,
284}
285
286impl<I> IterStringData<I>
287where
288    I: Iterator<Item = String>,
289{
290    /// Create from an iterator with explicit length.
291    pub fn from_iter(iter: I, len: usize) -> Self {
292        Self {
293            state: IterState::new(iter, len),
294        }
295    }
296}
297
298impl<I> IterStringData<I>
299where
300    I: ExactSizeIterator<Item = String>,
301{
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> AltrepLen for IterStringData<I>
311where
312    I: Iterator<Item = String>,
313{
314    fn len(&self) -> usize {
315        self.state.len()
316    }
317}
318
319impl<I> AltStringData for IterStringData<I>
320where
321    I: Iterator<Item = String>,
322{
323    fn elt(&self, i: usize) -> Option<&str> {
324        // Materialize to get stable storage for &str references
325        // This is necessary because we can't return &str from RefCell borrows
326        let materialized = self.state.materialize_all();
327        materialized.get(i).map(|s| s.as_str())
328    }
329}
330
331/// Iterator-backed list vector data adaptor.
332///
333/// Wraps an iterator producing R `SEXP` values and implements the data-level
334/// traits ([`AltrepLen`] + [`AltListData`]) for backing an ALTREP list. To
335/// expose it to R, wrap it in a `#[derive(AltrepList)]` + `#[altrep(manual)]`
336/// struct (see the [module docs](crate::altrep_data::iter)).
337///
338/// # Safety
339///
340/// The iterator must produce valid, protected SEXP values. Each SEXP must remain
341/// protected for the lifetime of the ALTREP object.
342///
343/// # Example
344///
345/// ```ignore
346/// use miniextendr_api::altrep_data::IterListData;
347/// use miniextendr_api::IntoR;
348///
349/// let iter = (0..5).map(|x| vec![x, x+1, x+2].into_sexp());
350/// let data = IterListData::from_iter(iter, 5);
351/// ```
352pub struct IterListData<I>
353where
354    I: Iterator<Item = SEXP>,
355{
356    state: IterState<I, SEXP>,
357}
358
359impl<I> IterListData<I>
360where
361    I: Iterator<Item = SEXP>,
362{
363    /// Create from an iterator with explicit length.
364    ///
365    /// # Safety
366    ///
367    /// The iterator must produce valid, protected SEXP values.
368    pub fn from_iter(iter: I, len: usize) -> Self {
369        Self {
370            state: IterState::new(iter, len),
371        }
372    }
373}
374
375impl<I> IterListData<I>
376where
377    I: ExactSizeIterator<Item = SEXP>,
378{
379    /// Create from an ExactSizeIterator (length auto-detected).
380    ///
381    /// # Safety
382    ///
383    /// The iterator must produce valid, protected SEXP values.
384    pub fn from_exact_iter(iter: I) -> Self {
385        Self {
386            state: IterState::from_exact_size(iter),
387        }
388    }
389}
390
391impl<I> AltrepLen for IterListData<I>
392where
393    I: Iterator<Item = SEXP>,
394{
395    fn len(&self) -> usize {
396        self.state.len()
397    }
398}
399
400impl<I> AltListData for IterListData<I>
401where
402    I: Iterator<Item = SEXP>,
403{
404    fn elt(&self, i: usize) -> SEXP {
405        use crate::SEXP;
406        self.state.get_element(i).unwrap_or(SEXP::nil())
407    }
408}
409
410/// Iterator-backed complex number vector data adaptor.
411///
412/// Wraps an iterator producing `Rcomplex` values and implements the data-level
413/// traits ([`AltrepLen`] + [`AltComplexData`]) for backing an ALTREP complex
414/// vector. To expose it to R, wrap it in a `#[derive(AltrepComplex)]` +
415/// `#[altrep(manual)]` struct (see the [module docs](crate::altrep_data::iter)).
416///
417/// # Example
418///
419/// ```ignore
420/// use miniextendr_api::altrep_data::IterComplexData;
421/// use miniextendr_api::Rcomplex;
422///
423/// let iter = (0..5).map(|x| Rcomplex { r: x as f64, i: (x * 2) as f64 });
424/// let data = IterComplexData::from_iter(iter, 5);
425/// ```
426pub struct IterComplexData<I>
427where
428    I: Iterator<Item = crate::Rcomplex>,
429{
430    state: IterState<I, crate::Rcomplex>,
431}
432
433impl<I> IterComplexData<I>
434where
435    I: Iterator<Item = crate::Rcomplex>,
436{
437    /// Create from an iterator with explicit length.
438    pub fn from_iter(iter: I, len: usize) -> Self {
439        Self {
440            state: IterState::new(iter, len),
441        }
442    }
443}
444
445impl<I> IterComplexData<I>
446where
447    I: ExactSizeIterator<Item = crate::Rcomplex>,
448{
449    /// Create from an ExactSizeIterator (length auto-detected).
450    pub fn from_exact_iter(iter: I) -> Self {
451        Self {
452            state: IterState::from_exact_size(iter),
453        }
454    }
455}
456
457impl<I> AltrepLen for IterComplexData<I>
458where
459    I: Iterator<Item = crate::Rcomplex>,
460{
461    fn len(&self) -> usize {
462        self.state.len()
463    }
464}
465
466impl<I> AltComplexData for IterComplexData<I>
467where
468    I: Iterator<Item = crate::Rcomplex>,
469{
470    fn elt(&self, i: usize) -> crate::Rcomplex {
471        self.state.get_element(i).unwrap_or(crate::Rcomplex {
472            r: f64::NAN,
473            i: f64::NAN,
474        })
475    }
476
477    fn as_slice(&self) -> Option<&[crate::Rcomplex]> {
478        self.state.as_materialized()
479    }
480
481    fn get_region(&self, start: usize, len: usize, buf: &mut [crate::Rcomplex]) -> usize {
482        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
483    }
484}