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