Skip to main content

miniextendr_api/altrep_data/
traits.rs

1//! Per-family ALTREP data traits.
2//!
3//! Each ALTREP family has a high-level data trait that users implement:
4//!
5//! | Trait | R Type | Key Method |
6//! |-------|--------|-----------|
7//! | [`AltIntegerData`] | INTSXP | `elt(i) -> i32` |
8//! | [`AltRealData`] | REALSXP | `elt(i) -> f64` |
9//! | [`AltLogicalData`] | LGLSXP | `elt(i) -> Logical` |
10//! | [`AltRawData`] | RAWSXP | `elt(i) -> u8` |
11//! | [`AltComplexData`] | CPLXSXP | `elt(i) -> Rcomplex` |
12//! | [`AltStringData`] | STRSXP | `elt(i) -> Option<&str>` |
13//! | [`AltListData`] | VECSXP | `elt(i) -> SEXP` |
14
15use super::{AltrepLen, Logical, Sortedness, fill_region};
16use crate::{Rcomplex, SEXP};
17
18// region: Integer ALTREP
19
20/// Trait for types that can back an ALTINTEGER vector.
21///
22/// Implement this to create custom integer ALTREP classes.
23pub trait AltIntegerData: AltrepLen {
24    /// Get the integer element at index `i`.
25    fn elt(&self, i: usize) -> i32;
26
27    /// Optional: return a pointer to contiguous data if available.
28    /// Default returns None (no contiguous backing).
29    fn as_slice(&self) -> Option<&[i32]> {
30        None
31    }
32
33    /// Optional: bulk read into buffer. Returns number of elements read.
34    ///
35    /// Bounds are clamped to the vector length; see `fill_region` for the
36    /// shared safety contract.
37    ///
38    /// R calls this when scanning the vector in bulk (`sum()`, `anyNA()`,
39    /// `duplicated()`, printing, ...), usually in blocks of up to 512
40    /// elements via `ITERATE_BY_REGION`, but occasionally with `len` equal
41    /// to the full vector length — never assume a block size. Calls are
42    /// synchronous pulls on the R main thread; a class cannot schedule or
43    /// prefetch them.
44    ///
45    /// The default fills element-by-element from [`elt`](Self::elt), which
46    /// already costs only one FFI dispatch per block. Override it when the
47    /// block has structure a per-element accessor would recompute (hoistable
48    /// per-block invariants, contiguous sub-ranges, chunked decoding).
49    ///
50    /// # Contract
51    ///
52    /// Fill at most `len.min(buf.len()).min(self.len() - start)` elements
53    /// starting at `start`, and return the count actually written. A `start`
54    /// at or past the end returns `0`.
55    ///
56    /// # Examples
57    ///
58    /// A lazy arithmetic sequence with a bulk fill that hoists the block
59    /// base out of the loop:
60    ///
61    /// ```
62    /// use miniextendr_api::altrep_data::{AltIntegerData, AltrepLen};
63    ///
64    /// /// Lazy arithmetic sequence: element i is `start + i * step`.
65    /// struct LazySeq { start: i32, step: i32, len: usize }
66    ///
67    /// impl AltrepLen for LazySeq {
68    ///     fn len(&self) -> usize { self.len }
69    /// }
70    ///
71    /// impl AltIntegerData for LazySeq {
72    ///     fn elt(&self, i: usize) -> i32 {
73    ///         self.start + (i as i32) * self.step
74    ///     }
75    ///
76    ///     /// Bulk fill: clamp once, hoist the block base out of the loop,
77    ///     /// then write `buf` sequentially (cache-friendly, no per-element
78    ///     /// bounds or dispatch overhead).
79    ///     fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
80    ///         let n = len.min(buf.len()).min(self.len.saturating_sub(start));
81    ///         let base = self.start + (start as i32) * self.step;
82    ///         for (k, slot) in buf[..n].iter_mut().enumerate() {
83    ///             *slot = base + (k as i32) * self.step;
84    ///         }
85    ///         n
86    ///     }
87    /// }
88    ///
89    /// let seq = LazySeq { start: 10, step: 2, len: 100 };
90    /// let mut buf = [0i32; 8];
91    ///
92    /// // Interior block: fully filled.
93    /// assert_eq!(seq.get_region(5, 8, &mut buf), 8);
94    /// assert_eq!(buf, [20, 22, 24, 26, 28, 30, 32, 34]);
95    ///
96    /// // Tail block: clamped to the vector length.
97    /// assert_eq!(seq.get_region(96, 8, &mut buf), 4);
98    /// assert_eq!(&buf[..4], &[202, 204, 206, 208]);
99    ///
100    /// // Past the end: nothing written.
101    /// assert_eq!(seq.get_region(100, 8, &mut buf), 0);
102    /// ```
103    fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
104        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
105    }
106
107    /// Optional: sortedness hint. Default is unknown.
108    fn is_sorted(&self) -> Option<Sortedness> {
109        None
110    }
111
112    /// Optional: does this vector contain any NA values?
113    fn no_na(&self) -> Option<bool> {
114        None
115    }
116
117    /// Optional: optimized sum. Default returns None (use R's default).
118    fn sum(&self, _na_rm: bool) -> Option<i64> {
119        None
120    }
121
122    /// Optional: optimized min. Default returns None (use R's default).
123    fn min(&self, _na_rm: bool) -> Option<i32> {
124        None
125    }
126
127    /// Optional: optimized max. Default returns None (use R's default).
128    fn max(&self, _na_rm: bool) -> Option<i32> {
129        None
130    }
131}
132// endregion
133
134// region: Real ALTREP
135
136/// Trait for types that can back an ALTREAL vector.
137pub trait AltRealData: AltrepLen {
138    /// Get the real element at index `i`.
139    fn elt(&self, i: usize) -> f64;
140
141    /// Optional: return a pointer to contiguous data if available.
142    fn as_slice(&self) -> Option<&[f64]> {
143        None
144    }
145
146    /// Optional: bulk read into buffer (clamped to available data).
147    ///
148    /// Same contract and override guidance as
149    /// [`AltIntegerData::get_region`]: fill at most
150    /// `len.min(buf.len()).min(self.len() - start)` elements and return the
151    /// count written.
152    fn get_region(&self, start: usize, len: usize, buf: &mut [f64]) -> usize {
153        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
154    }
155
156    /// Optional: sortedness hint.
157    fn is_sorted(&self) -> Option<Sortedness> {
158        None
159    }
160
161    /// Optional: does this vector contain any NA values?
162    fn no_na(&self) -> Option<bool> {
163        None
164    }
165
166    /// Optional: optimized sum.
167    fn sum(&self, _na_rm: bool) -> Option<f64> {
168        None
169    }
170
171    /// Optional: optimized min.
172    fn min(&self, _na_rm: bool) -> Option<f64> {
173        None
174    }
175
176    /// Optional: optimized max.
177    fn max(&self, _na_rm: bool) -> Option<f64> {
178        None
179    }
180}
181// endregion
182
183// region: Logical ALTREP
184
185/// Trait for types that can back an ALTLOGICAL vector.
186pub trait AltLogicalData: AltrepLen {
187    /// Get the logical element at index `i`.
188    fn elt(&self, i: usize) -> Logical;
189
190    /// Optional: return a slice if data is contiguous i32 (R's internal format).
191    fn as_r_slice(&self) -> Option<&[i32]> {
192        None
193    }
194
195    /// Optional: bulk read into buffer (clamped to available data).
196    ///
197    /// The buffer is R's native logical storage (`i32`: 0, 1, or
198    /// `NA_LOGICAL`). Same contract and override guidance as
199    /// [`AltIntegerData::get_region`].
200    fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
201        fill_region(start, len, self.len(), buf, |idx| self.elt(idx).to_r_int())
202    }
203
204    /// Optional: sortedness hint.
205    fn is_sorted(&self) -> Option<Sortedness> {
206        None
207    }
208
209    /// Optional: does this vector contain any NA values?
210    fn no_na(&self) -> Option<bool> {
211        None
212    }
213
214    /// Optional: optimized sum (count of TRUE values).
215    fn sum(&self, _na_rm: bool) -> Option<i64> {
216        None
217    }
218    // Note: R's ALTREP API does not expose min/max for logical vectors
219}
220// endregion
221
222// region: Raw ALTREP
223
224/// Trait for types that can back an ALTRAW vector.
225pub trait AltRawData: AltrepLen {
226    /// Get the raw byte at index `i`.
227    fn elt(&self, i: usize) -> u8;
228
229    /// Optional: return a slice if data is contiguous.
230    fn as_slice(&self) -> Option<&[u8]> {
231        None
232    }
233
234    /// Optional: bulk read into buffer (clamped to available data).
235    ///
236    /// Same contract and override guidance as
237    /// [`AltIntegerData::get_region`].
238    fn get_region(&self, start: usize, len: usize, buf: &mut [u8]) -> usize {
239        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
240    }
241}
242// endregion
243
244// region: Complex ALTREP
245
246/// Trait for types that can back an ALTCOMPLEX vector.
247pub trait AltComplexData: AltrepLen {
248    /// Get the complex element at index `i`.
249    fn elt(&self, i: usize) -> Rcomplex;
250
251    /// Optional: return a slice if data is contiguous.
252    fn as_slice(&self) -> Option<&[Rcomplex]> {
253        None
254    }
255
256    /// Optional: bulk read into buffer (clamped to available data).
257    ///
258    /// Same contract and override guidance as
259    /// [`AltIntegerData::get_region`].
260    fn get_region(&self, start: usize, len: usize, buf: &mut [Rcomplex]) -> usize {
261        fill_region(start, len, self.len(), buf, |idx| self.elt(idx))
262    }
263}
264// endregion
265
266// region: String ALTREP
267
268/// Trait for types that can back an ALTSTRING vector.
269///
270/// Note: `elt` returns a `&str` which will be converted to CHARSXP.
271pub trait AltStringData: AltrepLen {
272    /// Get the string element at index `i`.
273    ///
274    /// Return `None` for NA values.
275    fn elt(&self, i: usize) -> Option<&str>;
276
277    /// Optional: sortedness hint.
278    fn is_sorted(&self) -> Option<Sortedness> {
279        None
280    }
281
282    /// Optional: does this vector contain any NA values?
283    fn no_na(&self) -> Option<bool> {
284        None
285    }
286}
287// endregion
288
289// region: List ALTREP
290
291/// Trait for types that can back an ALTLIST vector.
292///
293/// List elements are arbitrary SEXPs, so this trait works with raw SEXP.
294pub trait AltListData: AltrepLen {
295    /// Get the list element at index `i`.
296    ///
297    /// Returns a SEXP (any R object).
298    fn elt(&self, i: usize) -> SEXP;
299}
300// endregion