miniextendr_api/altrep_traits.rs
1//! Safe, idiomatic ALTREP trait hierarchy mirroring R's method tables.
2//!
3//! ## Design: Required vs Optional Methods
4//!
5//! Each ALTREP type family has:
6//! - **Required methods** (no defaults) - compiler enforces implementation
7//! - **Optional methods** with `HAS_*` const gating - defaults to `false` (not installed)
8//!
9//! When `HAS_*` is false, the method is NOT installed with R, so R uses its own default behavior.
10//!
11//! ## Required Methods by Type
12//!
13//! | Type | Required Methods |
14//! |------|------------------|
15//! | All | `length` |
16//! | ALTSTRING | `length` + `elt` |
17//! | ALTLIST | `length` + `elt` |
18//! | Numeric types | `length` + (`elt` OR `dataptr` via HAS_*) |
19//!
20//! ## Guard mode ([`Altrep::GUARD`])
21//!
22//! Every ALTREP callback installed by this crate is wrapped according to the
23//! `const GUARD: AltrepGuard` on the implementing type. The three modes are
24//! selected per-type (not per-callback) and resolved at compile time:
25//!
26//! | Mode | What it wraps | Use when |
27//! |---|---|---|
28//! | [`AltrepGuard::Unsafe`] | nothing | callback is trivial, allocation-free, and cannot panic |
29//! | [`AltrepGuard::RustUnwind`] | [`catch_unwind`](std::panic::catch_unwind) | pure-Rust callback that may panic but never enters R API code |
30//! | [`AltrepGuard::RUnwind`] *(default)* | `R_UnwindProtect` (R's C API) | callback calls into R (e.g. `Rf_mkCharLenCE`, `Rf_allocVector`) and may longjmp |
31//!
32//! The derive surface exposes the same three modes as `#[altrep(unsafe)]` /
33//! `#[altrep(rust_unwind)]` / `#[altrep(r_unwind)]`; bridging through these
34//! modes is handled by [`crate::altrep_bridge`].
35
36use crate::{R_xlen_t, Rcomplex, SEXP, SEXPTYPE};
37use core::ffi::c_void;
38
39// region: ALTREP GUARD MODE
40
41/// Controls the panic/error guard used around ALTREP trampoline callbacks.
42///
43/// Each mode trades off safety vs performance:
44///
45/// - [`Unsafe`](AltrepGuard::Unsafe): No protection. If the callback panics,
46/// behavior is undefined (unwinding through C frames). Use only for trivial
47/// callbacks that cannot panic.
48///
49/// - [`RustUnwind`](AltrepGuard::RustUnwind): Wraps in `catch_unwind`, converting
50/// Rust panics to R errors. This is the **default** and safe for all pure-Rust
51/// callbacks. Overhead: ~1-2ns per call.
52///
53/// - [`RUnwind`](AltrepGuard::RUnwind): Wraps in `R_UnwindProtect`, catching both
54/// Rust panics and R `longjmp` errors. Use when ALTREP callbacks invoke R API
55/// functions that might error (e.g., `Rf_allocVector`, `Rf_eval`).
56///
57/// The guard is selected via the `const GUARD` associated constant on the [`Altrep`]
58/// trait. Since it is a const, the compiler eliminates dead branches at
59/// monomorphization time — zero runtime overhead for the chosen mode.
60#[derive(Clone, Copy, PartialEq, Eq, Debug)]
61pub enum AltrepGuard {
62 /// No protection. Fastest, but if the callback panics, behavior is undefined.
63 Unsafe,
64 /// `catch_unwind` — catches Rust panics, converts to R errors. Default.
65 RustUnwind,
66 /// `with_r_unwind_protect` — catches both Rust panics and R longjmps.
67 /// Use when ALTREP callbacks invoke R API functions that might error.
68 RUnwind,
69}
70// endregion
71
72// region: ALTREP BASE
73
74/// Base ALTREP methods.
75///
76/// `length` is REQUIRED (no default). All other methods are optional with HAS_* gating.
77pub trait Altrep {
78 /// The guard mode for all ALTREP trampolines on this type.
79 ///
80 /// Defaults to [`AltrepGuard::RUnwind`] which catches both Rust panics and
81 /// R longjmps via `R_UnwindProtect`. This is safe for all callbacks, including
82 /// those that call R API functions (e.g., `Rf_mkCharLenCE`, `Rf_ScalarReal`,
83 /// `into_sexp` in serialization).
84 ///
85 /// Override to [`AltrepGuard::RustUnwind`] if your callbacks never call R APIs
86 /// and you want to save ~2ns per call, or [`AltrepGuard::Unsafe`] for trivial
87 /// callbacks that cannot panic.
88 const GUARD: AltrepGuard = AltrepGuard::RUnwind;
89
90 // --- REQUIRED ---
91 /// Returns the length of the ALTREP vector.
92 /// This is REQUIRED - R cannot determine vector length without it.
93 fn length(x: SEXP) -> R_xlen_t;
94
95 // --- OPTIONAL: Serialization ---
96 /// Set to `true` to register [`serialized_state`](Self::serialized_state).
97 const HAS_SERIALIZED_STATE: bool = false;
98 /// Return serialization state.
99 fn serialized_state(_x: SEXP) -> SEXP {
100 unreachable!("HAS_SERIALIZED_STATE = false")
101 }
102
103 /// Set to `true` to register [`unserialize`](Self::unserialize).
104 const HAS_UNSERIALIZE: bool = false;
105 /// Reconstruct ALTREP from serialized state.
106 fn unserialize(_class: SEXP, _state: SEXP) -> SEXP {
107 unreachable!("HAS_UNSERIALIZE = false")
108 }
109
110 /// Set to `true` to register [`unserialize_ex`](Self::unserialize_ex).
111 const HAS_UNSERIALIZE_EX: bool = false;
112 /// Extended unserialization with attributes.
113 fn unserialize_ex(_class: SEXP, _state: SEXP, _attr: SEXP, _objf: i32, _levs: i32) -> SEXP {
114 unreachable!("HAS_UNSERIALIZE_EX = false")
115 }
116
117 // --- OPTIONAL: Duplication ---
118 /// Set to `true` to register [`duplicate`](Self::duplicate).
119 const HAS_DUPLICATE: bool = false;
120 /// Duplicate the ALTREP object.
121 fn duplicate(_x: SEXP, _deep: bool) -> SEXP {
122 unreachable!("HAS_DUPLICATE = false")
123 }
124
125 /// Set to `true` to register [`duplicate_ex`](Self::duplicate_ex).
126 const HAS_DUPLICATE_EX: bool = false;
127 /// Extended duplication.
128 fn duplicate_ex(_x: SEXP, _deep: bool) -> SEXP {
129 unreachable!("HAS_DUPLICATE_EX = false")
130 }
131
132 // --- OPTIONAL: Coercion ---
133 /// Set to `true` to register [`coerce`](Self::coerce).
134 const HAS_COERCE: bool = false;
135 /// Coerce to another type.
136 fn coerce(_x: SEXP, _to_type: SEXPTYPE) -> SEXP {
137 unreachable!("HAS_COERCE = false")
138 }
139
140 // --- OPTIONAL: Inspection ---
141 /// Set to `true` to register [`inspect`](Self::inspect).
142 const HAS_INSPECT: bool = false;
143 /// Custom inspection for `.Internal(inspect())`.
144 fn inspect(
145 _x: SEXP,
146 _pre: i32,
147 _deep: i32,
148 _pvec: i32,
149 _inspect_subtree: Option<unsafe extern "C-unwind" fn(SEXP, i32, i32, i32)>,
150 ) -> bool {
151 unreachable!("HAS_INSPECT = false")
152 }
153}
154// endregion
155
156// region: ALTVEC - Vector-level methods (extends Altrep)
157
158/// Vector-level methods.
159///
160/// All methods are optional with HAS_* gating.
161pub trait AltVec: Altrep {
162 /// Set to `true` to register [`dataptr`](Self::dataptr).
163 const HAS_DATAPTR: bool = false;
164 /// Get raw data pointer.
165 fn dataptr(_x: SEXP, _writable: bool) -> *mut c_void {
166 unreachable!("HAS_DATAPTR = false")
167 }
168
169 /// Set to `true` to register [`dataptr_or_null`](Self::dataptr_or_null).
170 const HAS_DATAPTR_OR_NULL: bool = false;
171 /// Get data pointer without forcing materialization.
172 fn dataptr_or_null(_x: SEXP) -> *const c_void {
173 unreachable!("HAS_DATAPTR_OR_NULL = false")
174 }
175
176 /// Set to `true` to register [`extract_subset`](Self::extract_subset).
177 const HAS_EXTRACT_SUBSET: bool = false;
178 /// Optimized subsetting.
179 fn extract_subset(_x: SEXP, _indx: SEXP, _call: SEXP) -> SEXP {
180 unreachable!("HAS_EXTRACT_SUBSET = false")
181 }
182}
183// endregion
184
185// region: ALTINTEGER - Integer vector methods
186
187/// Integer vector methods.
188///
189/// For ALTINTEGER, you must provide EITHER:
190/// - `HAS_ELT = true` with `elt()` implementation, OR
191/// - `HAS_DATAPTR = true` with `dataptr()` implementation
192///
193/// If neither is provided, R will error at runtime when accessing elements.
194pub trait AltInteger: AltVec {
195 /// Set to `true` to register [`elt`](Self::elt).
196 const HAS_ELT: bool = false;
197 /// Get element at index.
198 fn elt(_x: SEXP, _i: R_xlen_t) -> i32 {
199 unreachable!("HAS_ELT = false")
200 }
201
202 /// Set to `true` to register [`get_region`](Self::get_region).
203 const HAS_GET_REGION: bool = false;
204 /// Bulk read elements into buffer.
205 fn get_region(_x: SEXP, _i: R_xlen_t, _n: R_xlen_t, _buf: &mut [i32]) -> R_xlen_t {
206 unreachable!("HAS_GET_REGION = false")
207 }
208
209 /// Set to `true` to register [`is_sorted`](Self::is_sorted).
210 const HAS_IS_SORTED: bool = false;
211 /// Sortedness hint.
212 fn is_sorted(_x: SEXP) -> i32 {
213 unreachable!("HAS_IS_SORTED = false")
214 }
215
216 /// Set to `true` to register [`no_na`](Self::no_na).
217 const HAS_NO_NA: bool = false;
218 /// NA-free hint.
219 fn no_na(_x: SEXP) -> i32 {
220 unreachable!("HAS_NO_NA = false")
221 }
222
223 /// Set to `true` to register [`sum`](Self::sum).
224 const HAS_SUM: bool = false;
225 /// Optimized sum.
226 fn sum(_x: SEXP, _narm: bool) -> SEXP {
227 unreachable!("HAS_SUM = false")
228 }
229
230 /// Set to `true` to register [`min`](Self::min).
231 const HAS_MIN: bool = false;
232 /// Optimized min.
233 fn min(_x: SEXP, _narm: bool) -> SEXP {
234 unreachable!("HAS_MIN = false")
235 }
236
237 /// Set to `true` to register [`max`](Self::max).
238 const HAS_MAX: bool = false;
239 /// Optimized max.
240 fn max(_x: SEXP, _narm: bool) -> SEXP {
241 unreachable!("HAS_MAX = false")
242 }
243}
244// endregion
245
246// region: ALTREAL - Real (double) vector methods
247
248/// Real vector methods.
249pub trait AltReal: AltVec {
250 /// Set to `true` to register [`elt`](Self::elt).
251 const HAS_ELT: bool = false;
252 /// Get element at index.
253 fn elt(_x: SEXP, _i: R_xlen_t) -> f64 {
254 unreachable!("HAS_ELT = false")
255 }
256
257 /// Set to `true` to register [`get_region`](Self::get_region).
258 const HAS_GET_REGION: bool = false;
259 /// Bulk read elements into buffer.
260 fn get_region(_x: SEXP, _i: R_xlen_t, _n: R_xlen_t, _buf: &mut [f64]) -> R_xlen_t {
261 unreachable!("HAS_GET_REGION = false")
262 }
263
264 /// Set to `true` to register [`is_sorted`](Self::is_sorted).
265 const HAS_IS_SORTED: bool = false;
266 /// Sortedness hint.
267 fn is_sorted(_x: SEXP) -> i32 {
268 unreachable!("HAS_IS_SORTED = false")
269 }
270
271 /// Set to `true` to register [`no_na`](Self::no_na).
272 const HAS_NO_NA: bool = false;
273 /// NA-free hint.
274 fn no_na(_x: SEXP) -> i32 {
275 unreachable!("HAS_NO_NA = false")
276 }
277
278 /// Set to `true` to register [`sum`](Self::sum).
279 const HAS_SUM: bool = false;
280 /// Optimized sum.
281 fn sum(_x: SEXP, _narm: bool) -> SEXP {
282 unreachable!("HAS_SUM = false")
283 }
284
285 /// Set to `true` to register [`min`](Self::min).
286 const HAS_MIN: bool = false;
287 /// Optimized min.
288 fn min(_x: SEXP, _narm: bool) -> SEXP {
289 unreachable!("HAS_MIN = false")
290 }
291
292 /// Set to `true` to register [`max`](Self::max).
293 const HAS_MAX: bool = false;
294 /// Optimized max.
295 fn max(_x: SEXP, _narm: bool) -> SEXP {
296 unreachable!("HAS_MAX = false")
297 }
298}
299// endregion
300
301// region: ALTLOGICAL - Logical vector methods
302
303/// Logical vector methods.
304pub trait AltLogical: AltVec {
305 /// Set to `true` to register [`elt`](Self::elt).
306 const HAS_ELT: bool = false;
307 /// Returns i32: 0=FALSE, 1=TRUE, NA_LOGICAL=NA
308 fn elt(_x: SEXP, _i: R_xlen_t) -> i32 {
309 unreachable!("HAS_ELT = false")
310 }
311
312 /// Set to `true` to register [`get_region`](Self::get_region).
313 const HAS_GET_REGION: bool = false;
314 /// Bulk read elements into buffer.
315 fn get_region(_x: SEXP, _i: R_xlen_t, _n: R_xlen_t, _buf: &mut [i32]) -> R_xlen_t {
316 unreachable!("HAS_GET_REGION = false")
317 }
318
319 /// Set to `true` to register [`is_sorted`](Self::is_sorted).
320 const HAS_IS_SORTED: bool = false;
321 /// Sortedness hint.
322 fn is_sorted(_x: SEXP) -> i32 {
323 unreachable!("HAS_IS_SORTED = false")
324 }
325
326 /// Set to `true` to register [`no_na`](Self::no_na).
327 const HAS_NO_NA: bool = false;
328 /// NA-free hint.
329 fn no_na(_x: SEXP) -> i32 {
330 unreachable!("HAS_NO_NA = false")
331 }
332
333 /// Set to `true` to register [`sum`](Self::sum).
334 const HAS_SUM: bool = false;
335 /// Sum for logical = count of TRUE values.
336 fn sum(_x: SEXP, _narm: bool) -> SEXP {
337 unreachable!("HAS_SUM = false")
338 }
339 // Note: R's ALTREP API does not expose min/max for logical vectors
340}
341// endregion
342
343// region: ALTRAW - Raw (byte) vector methods
344
345/// Raw vector methods.
346pub trait AltRaw: AltVec {
347 /// Set to `true` to register [`elt`](Self::elt).
348 const HAS_ELT: bool = false;
349 /// Get element at index.
350 fn elt(_x: SEXP, _i: R_xlen_t) -> u8 {
351 unreachable!("HAS_ELT = false")
352 }
353
354 /// Set to `true` to register [`get_region`](Self::get_region).
355 const HAS_GET_REGION: bool = false;
356 /// Bulk read elements into buffer.
357 fn get_region(_x: SEXP, _i: R_xlen_t, _n: R_xlen_t, _buf: &mut [u8]) -> R_xlen_t {
358 unreachable!("HAS_GET_REGION = false")
359 }
360}
361// endregion
362
363// region: ALTCOMPLEX - Complex vector methods
364
365/// Complex vector methods.
366pub trait AltComplex: AltVec {
367 /// Set to `true` to register [`elt`](Self::elt).
368 const HAS_ELT: bool = false;
369 /// Get element at index.
370 fn elt(_x: SEXP, _i: R_xlen_t) -> Rcomplex {
371 unreachable!("HAS_ELT = false")
372 }
373
374 /// Set to `true` to register [`get_region`](Self::get_region).
375 const HAS_GET_REGION: bool = false;
376 /// Bulk read elements into buffer.
377 fn get_region(_x: SEXP, _i: R_xlen_t, _n: R_xlen_t, _buf: &mut [Rcomplex]) -> R_xlen_t {
378 unreachable!("HAS_GET_REGION = false")
379 }
380}
381// endregion
382
383// region: ALTSTRING - String vector methods
384
385/// String vector methods.
386///
387/// **REQUIRED**: `elt` must be implemented (no default).
388/// R will error with "No Elt method found" if you don't provide it.
389pub trait AltString: AltVec {
390 // --- REQUIRED for ALTSTRING ---
391 /// Get string element at index. Returns CHARSXP.
392 /// This is REQUIRED for ALTSTRING - there is no default.
393 fn elt(x: SEXP, i: R_xlen_t) -> SEXP;
394
395 // --- OPTIONAL ---
396 /// Set to `true` to register [`set_elt`](Self::set_elt).
397 const HAS_SET_ELT: bool = false;
398 /// Set element (for mutable strings).
399 fn set_elt(_x: SEXP, _i: R_xlen_t, _v: SEXP) {
400 unreachable!("HAS_SET_ELT = false")
401 }
402
403 /// Set to `true` to register [`is_sorted`](Self::is_sorted).
404 const HAS_IS_SORTED: bool = false;
405 /// Sortedness hint.
406 fn is_sorted(_x: SEXP) -> i32 {
407 unreachable!("HAS_IS_SORTED = false")
408 }
409
410 /// Set to `true` to register [`no_na`](Self::no_na).
411 const HAS_NO_NA: bool = false;
412 /// NA-free hint.
413 fn no_na(_x: SEXP) -> i32 {
414 unreachable!("HAS_NO_NA = false")
415 }
416}
417// endregion
418
419// region: ALTLIST - List (VECSXP) methods
420
421/// List vector methods.
422///
423/// **REQUIRED**: `elt` must be implemented (no default).
424/// R will error with "must provide an Elt method" if you don't provide it.
425pub trait AltList: AltVec {
426 // --- REQUIRED for ALTLIST ---
427 /// Get list element at index. Returns any SEXP.
428 /// This is REQUIRED for ALTLIST - there is no default.
429 fn elt(x: SEXP, i: R_xlen_t) -> SEXP;
430
431 // --- OPTIONAL ---
432 /// Set to `true` to register [`set_elt`](Self::set_elt).
433 const HAS_SET_ELT: bool = false;
434 /// Set element (for mutable lists).
435 fn set_elt(_x: SEXP, _i: R_xlen_t, _v: SEXP) {
436 unreachable!("HAS_SET_ELT = false")
437 }
438}
439// endregion
440
441// region: Constants
442
443/// Unknown sortedness value (INT_MIN in R).
444pub const UNKNOWN_SORTEDNESS: i32 = i32::MIN;
445
446/// Known to be unsorted (`KNOWN_UNSORTED` in R).
447pub const KNOWN_UNSORTED: i32 = 0;
448
449/// Sorted in increasing order, possibly with ties (`SORTED_INCR` in R).
450pub const SORTED_INCR: i32 = 1;
451
452/// Sorted in decreasing order, possibly with ties (`SORTED_DECR` in R).
453pub const SORTED_DECR: i32 = -1;
454
455/// Sorted in increasing order, with NAs first (`SORTED_INCR_NA_1ST` in R).
456pub const SORTED_INCR_NA_1ST: i32 = 2;
457
458/// Sorted in decreasing order, with NAs first (`SORTED_DECR_NA_1ST` in R).
459pub const SORTED_DECR_NA_1ST: i32 = -2;
460/// NA value for integers.
461pub const NA_INTEGER: i32 = i32::MIN;
462/// NA value for logical (same as integer in R).
463pub const NA_LOGICAL: i32 = i32::MIN;
464/// NA value for reals (IEEE NaN with R's NA payload).
465pub const NA_REAL: f64 = f64::from_bits(0x7FF0_0000_0000_07A2);
466// endregion