Skip to main content

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