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