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::sys::{self};
17use crate::{IsNA, SEXP};
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.
41///
42/// The `altrep_` prefix keeps importing this trait from changing ordinary
43/// `len()` / `is_empty()` calls on arrays, slices and their owners.
44pub trait AltrepLen {
45    /// Returns the length of this ALTREP vector.
46    fn altrep_len(&self) -> usize;
47
48    /// Returns true if the vector is empty.
49    fn altrep_is_empty(&self) -> bool {
50        self.altrep_len() == 0
51    }
52}
53
54// region: Logical value type
55
56/// Logical value: TRUE, FALSE, or NA.
57#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Logical {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                Logical::False => "False",
                Logical::True => "True",
                Logical::Na => "Na",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Logical { }
#[automatically_derived]
impl ::core::clone::Clone for Logical {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Logical { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for Logical { }
#[automatically_derived]
impl ::core::cmp::PartialEq for Logical {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Logical { }Eq)]
58pub enum Logical {
59    /// Logical false.
60    False,
61    /// Logical true.
62    True,
63    /// Missing logical value.
64    Na,
65}
66
67impl Logical {
68    /// Convert to R's integer representation.
69    #[inline]
70    pub fn to_r_int(self) -> i32 {
71        self.into()
72    }
73
74    /// Convert from R's integer representation.
75    #[inline]
76    pub fn from_r_int(i: i32) -> Self {
77        i.into()
78    }
79
80    /// Convert from Rust bool (no NA representation).
81    #[inline]
82    pub fn from_bool(b: bool) -> Self {
83        b.into()
84    }
85}
86
87/// Convert Logical to R's integer representation.
88impl From<Logical> for i32 {
89    fn from(logical: Logical) -> i32 {
90        match logical {
91            Logical::False => 0,
92            Logical::True => 1,
93            Logical::Na => i32::MIN,
94        }
95    }
96}
97
98/// Convert from R's integer representation to Logical.
99impl From<i32> for Logical {
100    fn from(i: i32) -> Self {
101        match i {
102            0 => Logical::False,
103            v if v.is_na() => Logical::Na,
104            _ => Logical::True,
105        }
106    }
107}
108
109/// Convert from Rust bool to Logical (no NA representation).
110impl From<bool> for Logical {
111    fn from(b: bool) -> Self {
112        if b { Logical::True } else { Logical::False }
113    }
114}
115
116/// Convert from RLogical (FFI type) to Logical (semantic type).
117impl From<crate::RLogical> for Logical {
118    fn from(r: crate::RLogical) -> Self {
119        Logical::from_r_int(r.to_i32())
120    }
121}
122
123/// Convert from Logical (semantic type) to RLogical (FFI type).
124impl From<Logical> for crate::RLogical {
125    fn from(l: Logical) -> Self {
126        crate::RLogical::from_i32(l.to_r_int())
127    }
128}
129// endregion
130
131// region: Sortedness hint
132
133/// Sortedness hint for ALTREP vectors.
134#[derive(#[automatically_derived]
impl ::core::fmt::Debug for Sortedness {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                Sortedness::Unknown => "Unknown",
                Sortedness::KnownUnsorted => "KnownUnsorted",
                Sortedness::Increasing => "Increasing",
                Sortedness::Decreasing => "Decreasing",
                Sortedness::IncreasingNaFirst => "IncreasingNaFirst",
                Sortedness::DecreasingNaFirst => "DecreasingNaFirst",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Sortedness { }
#[automatically_derived]
impl ::core::clone::Clone for Sortedness {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Sortedness { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for Sortedness { }
#[automatically_derived]
impl ::core::cmp::PartialEq for Sortedness {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Sortedness { }Eq)]
135pub enum Sortedness {
136    /// Unknown sortedness.
137    Unknown,
138    /// Known to be unsorted.
139    ///
140    /// This corresponds to `KNOWN_UNSORTED` in R.
141    KnownUnsorted,
142    /// Sorted in increasing order (may have ties).
143    Increasing,
144    /// Sorted in decreasing order (may have ties).
145    Decreasing,
146    /// Sorted in increasing order, with NAs first.
147    ///
148    /// This corresponds to `SORTED_INCR_NA_1ST` in R.
149    IncreasingNaFirst,
150    /// Sorted in decreasing order, with NAs first.
151    ///
152    /// This corresponds to `SORTED_DECR_NA_1ST` in R.
153    DecreasingNaFirst,
154}
155
156impl Sortedness {
157    /// Convert to R's integer representation.
158    #[inline]
159    pub fn to_r_int(self) -> i32 {
160        self.into()
161    }
162
163    /// Convert from R's integer representation.
164    #[inline]
165    pub fn from_r_int(i: i32) -> Self {
166        i.into()
167    }
168}
169
170/// Convert Sortedness to R's integer representation.
171impl From<Sortedness> for i32 {
172    fn from(s: Sortedness) -> i32 {
173        match s {
174            Sortedness::Unknown => UNKNOWN_SORTEDNESS,
175            Sortedness::KnownUnsorted => KNOWN_UNSORTED,
176            Sortedness::Increasing => SORTED_INCR,
177            Sortedness::Decreasing => SORTED_DECR,
178            Sortedness::IncreasingNaFirst => SORTED_INCR_NA_1ST,
179            Sortedness::DecreasingNaFirst => SORTED_DECR_NA_1ST,
180        }
181    }
182}
183
184/// Convert R's integer sortedness code to Sortedness.
185impl From<i32> for Sortedness {
186    fn from(i: i32) -> Self {
187        match i {
188            KNOWN_UNSORTED => Sortedness::KnownUnsorted,
189            SORTED_INCR => Sortedness::Increasing,
190            SORTED_DECR => Sortedness::Decreasing,
191            SORTED_INCR_NA_1ST => Sortedness::IncreasingNaFirst,
192            SORTED_DECR_NA_1ST => Sortedness::DecreasingNaFirst,
193            _ => Sortedness::Unknown,
194        }
195    }
196}
197// endregion
198
199// region: Dataptr / serialization / subset helpers
200
201/// Trait for ALTREP types that can expose a data pointer.
202///
203/// # Writability contract
204///
205/// When `writable = true`, R **will** write through the returned pointer
206/// (e.g., `x[i] <- val`). The implementation must ensure:
207///
208/// 1. The returned pointer is safe to write to (not read-only memory).
209/// 2. Writes are visible to subsequent `Elt`/`Get_region` calls (no stale cache).
210///
211/// For owned containers (`Vec<T>`, `Box<[T]>`), this is automatic because
212/// DATAPTR and Elt both access the same allocation (data1).
213///
214/// For copy-on-write types (`Cow<'static, [T]>`), `writable = true` should
215/// trigger the copy so writes go to owned memory. When `writable = false`,
216/// the borrowed pointer can be returned directly.
217///
218/// For immutable data (`&'static [T]`), `writable = true` should panic or
219/// return `None` since the data cannot be modified.
220///
221/// The `__impl_altvec_dataptr` macro first tries `dataptr_or_null` for
222/// read-only access. If no contiguous buffer exists yet, it calls
223/// `dataptr(&mut self, false)`; writable requests call
224/// `dataptr(&mut self, true)` directly.
225pub trait AltrepDataptr<T> {
226    /// Get a pointer to the underlying data, possibly triggering materialization.
227    ///
228    /// When `writable` is true, R will write through the returned pointer.
229    /// Implementations for immutable data should panic or return `None`.
230    ///
231    /// Return `None` if data cannot be accessed as a contiguous buffer.
232    fn dataptr(&mut self, writable: bool) -> Option<*mut T>;
233
234    /// Get a read-only pointer without forcing materialization.
235    ///
236    /// Return `None` if data is not already materialized or cannot provide
237    /// a contiguous buffer. R will fall back to element-by-element access
238    /// via `Elt` when this returns `None`.
239    ///
240    /// The `__impl_altvec_dataptr` macro calls this for `Dataptr(x, writable=false)`
241    /// to avoid unnecessary mutable borrows and copy-on-write overhead.
242    fn dataptr_or_null(&self) -> Option<*const T> {
243        None
244    }
245}
246
247/// Return the bridge's R vector cache, if materialization has started.
248///
249/// Bridge-generated classes reserve data2 for a vector of the ALTREP's type.
250/// Once present, its values are the vector's: read them, not the Rust data.
251/// A string cache can be partial: slots not computed yet are C `NULL`, so
252/// read it through the string Elt callback, never directly.
253///
254/// # Safety
255/// `x` must be a live ALTREP using the bridge's data2 convention, on R's thread.
256#[doc(hidden)]
257pub unsafe fn materialized_altrep_data2(x: SEXP) -> Option<SEXP> {
258    use crate::SexpExt;
259    use crate::altrep_ext::AltrepSexpExt;
260
261    let data2 = unsafe { AltrepSexpExt::altrep_data2_raw(&x) };
262    (!data2.is_null_ptr() && data2.type_of() == x.type_of()).then_some(data2)
263}
264
265/// Whether R may have changed the vector's values since it was built.
266///
267/// Until then the Rust data is current, so a class may answer from it:
268/// serialize its state, subset it, report its sortedness, NAs, sum or range.
269/// A numeric cache exists only once `Dataptr` handed R a pointer it may
270/// write through, so for those families that is until data2 exists. A string
271/// cache also fills on reads, so a string ALTREP counts as written only once
272/// `Set_elt` or a writable `Dataptr` has marked its cache.
273///
274/// # Safety
275/// As for [`materialized_altrep_data2`].
276#[doc(hidden)]
277pub unsafe fn altrep_values_written(x: SEXP) -> bool {
278    use crate::SexpExt;
279
280    match unsafe { materialized_altrep_data2(x) } {
281        None => false,
282        Some(cache) if cache.type_of() == crate::SEXPTYPE::STRSXP => unsafe {
283            crate::altrep_impl::altstring_cache_written(cache)
284        },
285        Some(_) => true,
286    }
287}
288
289/// Read a region from a materialized atomic vector, without consulting data1.
290///
291/// # Safety
292/// `data2` must be a live plain R vector with element layout `T`, on R's thread.
293#[doc(hidden)]
294pub unsafe fn materialized_altrep_region<T: Copy>(
295    data2: SEXP,
296    start: usize,
297    len: usize,
298    out: &mut [T],
299) -> usize {
300    use crate::SexpExt;
301
302    let n = len.min(out.len()).min(data2.len().saturating_sub(start));
303    if n != 0 {
304        let ptr = unsafe { sys::DATAPTR_RO_unchecked(data2).cast::<T>().add(start) };
305        out[..n].copy_from_slice(unsafe { std::slice::from_raw_parts(ptr, n) });
306    }
307    n
308}
309
310/// Materialize an ALTREP SEXP into a plain R vector in data2.
311///
312/// Called by `__impl_altvec_dataptr` when the custom `dataptr()` returns `None`.
313/// Allocates a destination vector via `alloc_r_vector_unchecked`, fills it from
314/// `T::elt()` (which goes through R's ALTREP Elt dispatch), stores in data2,
315/// and returns DATAPTR of data2.
316///
317/// # Safety
318/// - `x` must be a valid ALTREP SEXP of element type `T`
319/// - Must be called on R's main thread
320pub unsafe fn materialize_altrep_data2<T: crate::RNativeType>(x: SEXP) -> *mut core::ffi::c_void {
321    use crate::SexpExt;
322    use crate::altrep_ext::AltrepSexpExt;
323
324    let n = x.len();
325    let (vec, dst) = unsafe { crate::into_r::alloc_r_vector_unchecked::<T>(n) };
326    unsafe { sys::Rf_protect_unchecked(vec) };
327    for (i, slot) in dst.iter_mut().enumerate() {
328        *slot = T::elt(x, i as isize);
329    }
330
331    unsafe {
332        AltrepSexpExt::set_altrep_data2(&x, vec);
333        sys::Rf_unprotect_unchecked(1);
334        sys::DATAPTR_RO_unchecked(vec).cast_mut()
335    }
336}
337
338/// Trait for ALTREP types that support serialization.
339///
340/// Saving and loading come as a pair: a class that writes its own state must
341/// be able to rebuild itself from it, or `readRDS()` of a saved vector fails.
342/// That is why the trait needs `Sized` (and `unserialize` has no default),
343/// although [`AltrepExtract`] accepts unsized views. A type that can't rebuild
344/// itself, such as a custom DST of runtime length, leaves serialization to R,
345/// which saves an ordinary vector.
346pub trait AltrepSerialize: Sized {
347    /// Convert the ALTREP data to a serializable R object.
348    fn serialized_state(&self) -> SEXP;
349
350    /// Reconstruct the ALTREP data from a serialized state.
351    ///
352    /// Return `None` if the state is invalid or cannot be deserialized.
353    fn unserialize(state: SEXP) -> Option<Self>;
354}
355
356/// Trait for ALTREP types that can provide optimized subsetting.
357pub trait AltrepExtractSubset {
358    /// Extract a subset of this ALTREP.
359    ///
360    /// `indices` contains 1-based R indices.
361    /// Return `None` to fall back to R's default subsetting.
362    fn extract_subset(&self, indices: &[i32]) -> Option<SEXP>;
363}
364// endregion
365
366// region: AltrepExtract - how to get &Self from an ALTREP SEXP
367
368/// How to extract a reference to `Self` from an ALTREP SEXP's data1 slot.
369///
370/// The default implementation (for types that implement `TypedExternal`) extracts
371/// via `ExternalPtr<T>` downcast from data1. Power users who want native SEXP
372/// storage can implement this trait manually, including for an unsized view
373/// that projects through a sized owner (`[T]` through `Box<[T]>`, a custom
374/// DST through its `Box`). Only the default `ExternalPtr` storage needs a
375/// sized type.
376///
377/// # Safety
378///
379/// Implementations must ensure that the returned references are valid for the
380/// duration of the ALTREP callback (i.e., the SEXP is protected by R's GC).
381///
382/// # Panics
383///
384/// The blanket implementation panics if ExternalPtr extraction fails (type
385/// mismatch, null pointer, etc.). This is a programmer error, not a runtime
386/// condition. Callers must ensure the ALTREP SEXP was created with the correct
387/// data type. The panic is caught by the ALTREP guard (`RustUnwind` or `RUnwind`)
388/// and converted to an R error. Using `AltrepGuard::Unsafe` with a type that
389/// can fail extraction is unsound.
390pub trait AltrepExtract {
391    /// Extract a shared reference from the ALTREP data1 slot.
392    ///
393    /// # Safety
394    ///
395    /// - `x` must be a valid ALTREP SEXP whose data1 holds data of type `Self`
396    /// - Must be called from R's main thread
397    unsafe fn altrep_extract_ref(x: crate::SEXP) -> &'static Self;
398
399    /// Extract a mutable reference from the ALTREP data1 slot.
400    ///
401    /// # Safety
402    ///
403    /// - `x` must be a valid ALTREP SEXP whose data1 holds data of type `Self`
404    /// - Must be called from R's main thread
405    /// - The caller must ensure no other references to the data exist
406    unsafe fn altrep_extract_mut(x: crate::SEXP) -> &'static mut Self;
407}
408
409/// Blanket implementation for types stored in ExternalPtr (the common case).
410///
411/// This is the default storage strategy: data1 is an EXTPTRSXP wrapping a
412/// `Box<Box<dyn Any>>` holding a `RefCell<T>`, read without a borrow.
413impl<T: crate::externalptr::TypedExternal> AltrepExtract for T {
414    unsafe fn altrep_extract_ref(x: crate::SEXP) -> &'static Self {
415        // SAFETY: ALTREP callbacks are always on R's main thread, so unchecked is safe.
416        // `ExternalPtr` is a non-owning wrapper around an R SEXP — dropping it does NOT
417        // deallocate the underlying data. The data lives in the ALTREP's data1 slot,
418        // which R's GC keeps alive for the duration of the callback. So the pointer
419        // derived from `ext.as_ref_unchecked()` remains valid after `ext` is dropped.
420        // The read takes no `RefCell` borrow until #2103 measures the checked one.
421        unsafe {
422            let ext = crate::altrep_data1_as_unchecked::<T>(x)
423                .expect("ALTREP data1 ExternalPtr extraction failed");
424            &*(ext.as_ref_unchecked() as *const T)
425        }
426    }
427
428    unsafe fn altrep_extract_mut(x: crate::SEXP) -> &'static mut Self {
429        // SAFETY: caller guarantees x is a valid ALTREP with ExternalPtr<T> in data1
430        // and that no other references exist. ALTREP callbacks are on R's main thread.
431        // `altrep_data1_mut_unchecked` reads the value's `RefCell` with no borrow
432        // and returns a 'static reference — sound because R's GC protects the
433        // ALTREP SEXP (and thus its data1) for the callback duration.
434        unsafe {
435            crate::altrep_data1_mut_unchecked::<T>(x)
436                .expect("ALTREP data1 mutable ExternalPtr extraction failed")
437        }
438    }
439}
440// endregion
441
442// region: InferBase trait - automatic base type inference from data traits
443
444/// Trait for inferring the R base type from a data type's implemented traits.
445///
446/// This is automatically implemented via blanket impls for types that implement
447/// one of the `Alt*Data` traits. It allows the `#[miniextendr]` macro to infer
448/// the base type without requiring an explicit `base = \"...\"` attribute.
449pub trait InferBase {
450    /// The inferred R base type.
451    const BASE: crate::altrep::RBase;
452
453    /// Create the ALTREP class handle.
454    ///
455    /// # Safety
456    /// Must be called during R initialization.
457    unsafe fn make_class(
458        class_name: *const ::core::ffi::c_char,
459        pkg_name: *const ::core::ffi::c_char,
460    ) -> crate::sys::altrep::R_altrep_class_t;
461
462    /// Install ALTREP methods on the class.
463    ///
464    /// # Safety
465    /// Must be called during R initialization with a valid class handle.
466    unsafe fn install_methods(cls: crate::sys::altrep::R_altrep_class_t);
467}
468// endregion