Skip to main content

miniextendr_api/altrep_data/
core.rs

1//! Core ALTREP data traits and helpers.
2//!
3//! Defines shared infrastructure used by all ALTREP families:
4//!
5//! - [`AltrepLen`] — length query trait
6//! - [`AltrepDataptr`] / [`AltrepSerialize`] / [`AltrepExtractSubset`] — optional capabilities
7//! - [`InferBase`] — maps data types to their R base type + class registration
8//! - [`Sortedness`] — sort-order metadata constants
9//! - [`Logical`] — three-valued logical (TRUE/FALSE/NA)
10//! - [`fill_region`] — helper for `get_region` implementations
11
12use crate::altrep_traits::{
13    KNOWN_UNSORTED, SORTED_DECR, SORTED_DECR_NA_1ST, SORTED_INCR, SORTED_INCR_NA_1ST,
14    UNKNOWN_SORTEDNESS,
15};
16use crate::ffi::SEXP;
17
18/// Helper for ALTREP `get_region` implementations.
19///
20/// R guarantees that the caller-provided buffer is at least `len` long. This
21/// helper clamps the requested range to the vector's total length and the
22/// actual buffer length, then fills `out` with values from the provided
23/// element accessor.
24#[inline]
25pub(crate) fn fill_region<T>(
26    start: usize,
27    len: usize,
28    total_len: usize,
29    out: &mut [T],
30    mut elt: impl FnMut(usize) -> T,
31) -> usize {
32    let n = len.min(out.len()).min(total_len.saturating_sub(start));
33    for (i, slot) in out.iter_mut().enumerate().take(n) {
34        *slot = elt(start + i);
35    }
36    n
37}
38
39/// Base trait for ALTREP data types. All ALTREP types must provide length.
40pub trait AltrepLen {
41    /// Returns the length of this ALTREP vector.
42    fn len(&self) -> usize;
43
44    /// Returns true if the vector is empty.
45    fn is_empty(&self) -> bool {
46        self.len() == 0
47    }
48}
49
50// region: Logical value type
51
52/// Logical value: TRUE, FALSE, or NA.
53#[derive(Debug, Clone, Copy, PartialEq, Eq)]
54pub enum Logical {
55    /// Logical false.
56    False,
57    /// Logical true.
58    True,
59    /// Missing logical value.
60    Na,
61}
62
63impl Logical {
64    /// Convert to R's integer representation.
65    #[inline]
66    pub fn to_r_int(self) -> i32 {
67        self.into()
68    }
69
70    /// Convert from R's integer representation.
71    #[inline]
72    pub fn from_r_int(i: i32) -> Self {
73        i.into()
74    }
75
76    /// Convert from Rust bool (no NA representation).
77    #[inline]
78    pub fn from_bool(b: bool) -> Self {
79        b.into()
80    }
81}
82
83/// Convert Logical to R's integer representation.
84impl From<Logical> for i32 {
85    fn from(logical: Logical) -> i32 {
86        match logical {
87            Logical::False => 0,
88            Logical::True => 1,
89            Logical::Na => i32::MIN,
90        }
91    }
92}
93
94/// Convert from R's integer representation to Logical.
95impl From<i32> for Logical {
96    fn from(i: i32) -> Self {
97        match i {
98            0 => Logical::False,
99            i32::MIN => Logical::Na,
100            _ => Logical::True,
101        }
102    }
103}
104
105/// Convert from Rust bool to Logical (no NA representation).
106impl From<bool> for Logical {
107    fn from(b: bool) -> Self {
108        if b { Logical::True } else { Logical::False }
109    }
110}
111
112/// Convert from RLogical (FFI type) to Logical (semantic type).
113impl From<crate::ffi::RLogical> for Logical {
114    fn from(r: crate::ffi::RLogical) -> Self {
115        Logical::from_r_int(r.to_i32())
116    }
117}
118
119/// Convert from Logical (semantic type) to RLogical (FFI type).
120impl From<Logical> for crate::ffi::RLogical {
121    fn from(l: Logical) -> Self {
122        crate::ffi::RLogical::from_i32(l.to_r_int())
123    }
124}
125// endregion
126
127// region: Sortedness hint
128
129/// Sortedness hint for ALTREP vectors.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub enum Sortedness {
132    /// Unknown sortedness.
133    Unknown,
134    /// Known to be unsorted.
135    ///
136    /// This corresponds to `KNOWN_UNSORTED` in R.
137    KnownUnsorted,
138    /// Sorted in increasing order (may have ties).
139    Increasing,
140    /// Sorted in decreasing order (may have ties).
141    Decreasing,
142    /// Sorted in increasing order, with NAs first.
143    ///
144    /// This corresponds to `SORTED_INCR_NA_1ST` in R.
145    IncreasingNaFirst,
146    /// Sorted in decreasing order, with NAs first.
147    ///
148    /// This corresponds to `SORTED_DECR_NA_1ST` in R.
149    DecreasingNaFirst,
150}
151
152impl Sortedness {
153    /// Convert to R's integer representation.
154    #[inline]
155    pub fn to_r_int(self) -> i32 {
156        self.into()
157    }
158
159    /// Convert from R's integer representation.
160    #[inline]
161    pub fn from_r_int(i: i32) -> Self {
162        i.into()
163    }
164}
165
166/// Convert Sortedness to R's integer representation.
167impl From<Sortedness> for i32 {
168    fn from(s: Sortedness) -> i32 {
169        match s {
170            Sortedness::Unknown => UNKNOWN_SORTEDNESS,
171            Sortedness::KnownUnsorted => KNOWN_UNSORTED,
172            Sortedness::Increasing => SORTED_INCR,
173            Sortedness::Decreasing => SORTED_DECR,
174            Sortedness::IncreasingNaFirst => SORTED_INCR_NA_1ST,
175            Sortedness::DecreasingNaFirst => SORTED_DECR_NA_1ST,
176        }
177    }
178}
179
180/// Convert R's integer sortedness code to Sortedness.
181impl From<i32> for Sortedness {
182    fn from(i: i32) -> Self {
183        match i {
184            KNOWN_UNSORTED => Sortedness::KnownUnsorted,
185            SORTED_INCR => Sortedness::Increasing,
186            SORTED_DECR => Sortedness::Decreasing,
187            SORTED_INCR_NA_1ST => Sortedness::IncreasingNaFirst,
188            SORTED_DECR_NA_1ST => Sortedness::DecreasingNaFirst,
189            _ => Sortedness::Unknown,
190        }
191    }
192}
193// endregion
194
195// region: Dataptr / serialization / subset helpers
196
197/// Trait for ALTREP types that can expose a data pointer.
198///
199/// # Writability contract
200///
201/// When `writable = true`, R **will** write through the returned pointer
202/// (e.g., `x[i] <- val`). The implementation must ensure:
203///
204/// 1. The returned pointer is safe to write to (not read-only memory).
205/// 2. Writes are visible to subsequent `Elt`/`Get_region` calls (no stale cache).
206///
207/// For owned containers (`Vec<T>`, `Box<[T]>`), this is automatic because
208/// DATAPTR and Elt both access the same allocation (data1).
209///
210/// For copy-on-write types (`Cow<'static, [T]>`), `writable = true` should
211/// trigger the copy so writes go to owned memory. When `writable = false`,
212/// the borrowed pointer can be returned directly.
213///
214/// For immutable data (`&'static [T]`), `writable = true` should panic or
215/// return `None` since the data cannot be modified.
216///
217/// The `__impl_altvec_dataptr` macro uses `dataptr_or_null` for read-only
218/// access and only calls `dataptr(&mut self, true)` when R requests a
219/// writable pointer.
220pub trait AltrepDataptr<T> {
221    /// Get a pointer to the underlying data, possibly triggering materialization.
222    ///
223    /// When `writable` is true, R will write through the returned pointer.
224    /// Implementations for immutable data should panic or return `None`.
225    ///
226    /// Return `None` if data cannot be accessed as a contiguous buffer.
227    fn dataptr(&mut self, writable: bool) -> Option<*mut T>;
228
229    /// Get a read-only pointer without forcing materialization.
230    ///
231    /// Return `None` if data is not already materialized or cannot provide
232    /// a contiguous buffer. R will fall back to element-by-element access
233    /// via `Elt` when this returns `None`.
234    ///
235    /// The `__impl_altvec_dataptr` macro calls this for `Dataptr(x, writable=false)`
236    /// to avoid unnecessary mutable borrows and copy-on-write overhead.
237    fn dataptr_or_null(&self) -> Option<*const T> {
238        None
239    }
240}
241
242/// Materialize an ALTREP SEXP into a plain R vector in data2.
243///
244/// Called by `__impl_altvec_dataptr` when the custom `dataptr()` returns `None`.
245/// Allocates a destination vector via `alloc_r_vector_unchecked`, fills it from
246/// `T::elt()` (which goes through R's ALTREP Elt dispatch), stores in data2,
247/// and returns DATAPTR of data2.
248///
249/// # Safety
250/// - `x` must be a valid ALTREP SEXP of element type `T`
251/// - Must be called on R's main thread
252pub unsafe fn materialize_altrep_data2<T: crate::ffi::RNativeType>(
253    x: SEXP,
254) -> *mut core::ffi::c_void {
255    use crate::altrep_ext::AltrepSexpExt;
256    use crate::ffi::{self, SexpExt};
257
258    let n = x.len();
259    let (vec, dst) = unsafe { crate::into_r::alloc_r_vector_unchecked::<T>(n) };
260    unsafe { ffi::Rf_protect_unchecked(vec) };
261    for (i, slot) in dst.iter_mut().enumerate() {
262        *slot = T::elt(x, i as isize);
263    }
264
265    unsafe {
266        AltrepSexpExt::set_altrep_data2(&x, vec);
267        ffi::Rf_unprotect_unchecked(1);
268        ffi::DATAPTR_RO_unchecked(vec).cast_mut()
269    }
270}
271
272/// Trait for ALTREP types that support serialization.
273pub trait AltrepSerialize: Sized {
274    /// Convert the ALTREP data to a serializable R object.
275    fn serialized_state(&self) -> SEXP;
276
277    /// Reconstruct the ALTREP data from a serialized state.
278    ///
279    /// Return `None` if the state is invalid or cannot be deserialized.
280    fn unserialize(state: SEXP) -> Option<Self>;
281}
282
283/// Trait for ALTREP types that can provide optimized subsetting.
284pub trait AltrepExtractSubset {
285    /// Extract a subset of this ALTREP.
286    ///
287    /// `indices` contains 1-based R indices.
288    /// Return `None` to fall back to R's default subsetting.
289    fn extract_subset(&self, indices: &[i32]) -> Option<SEXP>;
290}
291// endregion
292
293// region: AltrepExtract - how to get &Self from an ALTREP SEXP
294
295/// How to extract a reference to `Self` from an ALTREP SEXP's data1 slot.
296///
297/// The default implementation (for types that implement `TypedExternal`) extracts
298/// via `ExternalPtr<T>` downcast from data1. Power users who want native SEXP
299/// storage can implement this trait manually.
300///
301/// # Safety
302///
303/// Implementations must ensure that the returned references are valid for the
304/// duration of the ALTREP callback (i.e., the SEXP is protected by R's GC).
305///
306/// # Panics
307///
308/// The blanket implementation panics if ExternalPtr extraction fails (type
309/// mismatch, null pointer, etc.). This is a programmer error, not a runtime
310/// condition. Callers must ensure the ALTREP SEXP was created with the correct
311/// data type. The panic is caught by the ALTREP guard (`RustUnwind` or `RUnwind`)
312/// and converted to an R error. Using `AltrepGuard::Unsafe` with a type that
313/// can fail extraction is unsound.
314pub trait AltrepExtract: Sized {
315    /// Extract a shared reference from the ALTREP data1 slot.
316    ///
317    /// # Safety
318    ///
319    /// - `x` must be a valid ALTREP SEXP whose data1 holds data of type `Self`
320    /// - Must be called from R's main thread
321    unsafe fn altrep_extract_ref(x: crate::ffi::SEXP) -> &'static Self;
322
323    /// Extract a mutable reference from the ALTREP data1 slot.
324    ///
325    /// # Safety
326    ///
327    /// - `x` must be a valid ALTREP SEXP whose data1 holds data of type `Self`
328    /// - Must be called from R's main thread
329    /// - The caller must ensure no other references to the data exist
330    unsafe fn altrep_extract_mut(x: crate::ffi::SEXP) -> &'static mut Self;
331}
332
333/// Blanket implementation for types stored in ExternalPtr (the common case).
334///
335/// This is the default storage strategy: data1 is an EXTPTRSXP wrapping a
336/// `Box<Box<dyn Any>>` that downcasts to `&T`.
337impl<T: crate::externalptr::TypedExternal> AltrepExtract for T {
338    unsafe fn altrep_extract_ref(x: crate::ffi::SEXP) -> &'static Self {
339        // SAFETY: ALTREP callbacks are always on R's main thread, so unchecked is safe.
340        // `ExternalPtr` is a non-owning wrapper around an R SEXP — dropping it does NOT
341        // deallocate the underlying data. The data lives in the ALTREP's data1 slot,
342        // which R's GC keeps alive for the duration of the callback. So the pointer
343        // derived from `ext.as_ref()` remains valid after `ext` is dropped.
344        unsafe {
345            let ext = crate::altrep_data1_as_unchecked::<T>(x)
346                .expect("ALTREP data1 ExternalPtr extraction failed");
347            &*(ext.as_ref().unwrap() as *const T)
348        }
349    }
350
351    unsafe fn altrep_extract_mut(x: crate::ffi::SEXP) -> &'static mut Self {
352        // SAFETY: caller guarantees x is a valid ALTREP with ExternalPtr<T> in data1
353        // and that no other references exist. ALTREP callbacks are on R's main thread.
354        // `altrep_data1_mut_unchecked` goes through `ErasedExternalPtr::downcast_mut`
355        // which returns a reference with transmuted 'static lifetime — sound because
356        // R's GC protects the ALTREP SEXP (and thus its data1) for the callback duration.
357        unsafe {
358            crate::altrep_data1_mut_unchecked::<T>(x)
359                .expect("ALTREP data1 mutable ExternalPtr extraction failed")
360        }
361    }
362}
363// endregion
364
365// region: InferBase trait - automatic base type inference from data traits
366
367/// Trait for inferring the R base type from a data type's implemented traits.
368///
369/// This is automatically implemented via blanket impls for types that implement
370/// one of the `Alt*Data` traits. It allows the `#[miniextendr]` macro to infer
371/// the base type without requiring an explicit `base = \"...\"` attribute.
372pub trait InferBase {
373    /// The inferred R base type.
374    const BASE: crate::altrep::RBase;
375
376    /// Create the ALTREP class handle.
377    ///
378    /// # Safety
379    /// Must be called during R initialization.
380    unsafe fn make_class(
381        class_name: *const i8,
382        pkg_name: *const i8,
383    ) -> crate::ffi::altrep::R_altrep_class_t;
384
385    /// Install ALTREP methods on the class.
386    ///
387    /// # Safety
388    /// Must be called during R initialization with a valid class handle.
389    unsafe fn install_methods(cls: crate::ffi::altrep::R_altrep_class_t);
390}
391// endregion