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