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.altrep_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 altrep_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.altrep_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.altrep_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.altrep_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: `Some(true)` promises the vector holds no `NA` **and** no `NaN`.
162 ///
163 /// R reads this hint as "nothing `ISNAN`": `anyNA()` returns `FALSE` without
164 /// scanning when it is true, so a vector with a plain `NaN` must not report
165 /// `Some(true)` (`anyNA(c(1, NaN))` is `TRUE`).
166 fn no_na(&self) -> Option<bool> {
167 None
168 }
169
170 /// Optional: optimized sum.
171 fn sum(&self, _na_rm: bool) -> Option<f64> {
172 None
173 }
174
175 /// Optional: optimized min.
176 fn min(&self, _na_rm: bool) -> Option<f64> {
177 None
178 }
179
180 /// Optional: optimized max.
181 fn max(&self, _na_rm: bool) -> Option<f64> {
182 None
183 }
184}
185// endregion
186
187// region: Logical ALTREP
188
189/// Trait for types that can back an ALTLOGICAL vector.
190pub trait AltLogicalData: AltrepLen {
191 /// Get the logical element at index `i`.
192 fn elt(&self, i: usize) -> Logical;
193
194 /// Optional: return a slice if data is contiguous i32 (R's internal format).
195 ///
196 /// When present, the generated element callback reads this storage directly,
197 /// preserving raw nonzero values as well as the NA sentinel.
198 fn as_r_slice(&self) -> Option<&[i32]> {
199 None
200 }
201
202 /// Optional: bulk read into buffer (clamped to available data).
203 ///
204 /// The buffer is R's native logical storage (`i32`: 0, 1, or
205 /// `NA_LOGICAL`). Same contract and override guidance as
206 /// [`AltIntegerData::get_region`].
207 fn get_region(&self, start: usize, len: usize, buf: &mut [i32]) -> usize {
208 fill_region(start, len, self.altrep_len(), buf, |idx| {
209 self.elt(idx).to_r_int()
210 })
211 }
212
213 /// Optional: sortedness hint.
214 fn is_sorted(&self) -> Option<Sortedness> {
215 None
216 }
217
218 /// Optional: does this vector contain any NA values?
219 fn no_na(&self) -> Option<bool> {
220 None
221 }
222
223 /// Optional: optimized sum (count of TRUE values).
224 fn sum(&self, _na_rm: bool) -> Option<i64> {
225 None
226 }
227 // Note: R's ALTREP API does not expose min/max for logical vectors
228}
229// endregion
230
231// region: Raw ALTREP
232
233/// Trait for types that can back an ALTRAW vector.
234pub trait AltRawData: AltrepLen {
235 /// Get the raw byte at index `i`.
236 fn elt(&self, i: usize) -> u8;
237
238 /// Optional: return a slice if data is contiguous.
239 fn as_slice(&self) -> Option<&[u8]> {
240 None
241 }
242
243 /// Optional: bulk read into buffer (clamped to available data).
244 ///
245 /// Same contract and override guidance as
246 /// [`AltIntegerData::get_region`].
247 fn get_region(&self, start: usize, len: usize, buf: &mut [u8]) -> usize {
248 fill_region(start, len, self.altrep_len(), buf, |idx| self.elt(idx))
249 }
250}
251// endregion
252
253// region: Complex ALTREP
254
255/// Trait for types that can back an ALTCOMPLEX vector.
256pub trait AltComplexData: AltrepLen {
257 /// Get the complex element at index `i`.
258 fn elt(&self, i: usize) -> Rcomplex;
259
260 /// Optional: return a slice if data is contiguous.
261 fn as_slice(&self) -> Option<&[Rcomplex]> {
262 None
263 }
264
265 /// Optional: bulk read into buffer (clamped to available data).
266 ///
267 /// Same contract and override guidance as
268 /// [`AltIntegerData::get_region`].
269 fn get_region(&self, start: usize, len: usize, buf: &mut [Rcomplex]) -> usize {
270 fill_region(start, len, self.altrep_len(), buf, |idx| self.elt(idx))
271 }
272}
273// endregion
274
275// region: String ALTREP
276
277/// Trait for types that can back an ALTSTRING vector.
278///
279/// Note: `elt` returns a `&str` which will be converted to CHARSXP.
280pub trait AltStringData: AltrepLen {
281 /// Get the string element at index `i`.
282 ///
283 /// Return `None` for NA values.
284 fn elt(&self, i: usize) -> Option<&str>;
285
286 /// Optional: sortedness hint.
287 fn is_sorted(&self) -> Option<Sortedness> {
288 None
289 }
290
291 /// Optional: does this vector contain any NA values?
292 fn no_na(&self) -> Option<bool> {
293 None
294 }
295}
296// endregion
297
298// region: List ALTREP
299
300/// Trait for types that can back an ALTLIST vector.
301///
302/// List elements are arbitrary SEXPs, so this trait works with raw SEXP.
303pub trait AltListData: AltrepLen {
304 /// Get the list element at index `i`.
305 ///
306 /// Returns a SEXP (any R object).
307 fn elt(&self, i: usize) -> SEXP;
308}
309// endregion