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//!
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