Skip to main content

miniextendr_api/externalptr/
altrep_helpers.rs

1//! ALTREP helpers for `ExternalPtr` — data1/data2 slot access.
2//!
3//! Convenience functions for ALTREP implementations that store their data
4//! in `ExternalPtr` slots. Also provides the `Sidecar` marker type for
5//! `#[r_data]` fields.
6
7use super::{ErasedExternalPtr, ExternalPtr, TypedExternal};
8use crate::ffi::SEXP;
9
10/// Extract the ALTREP data1 slot as a typed `ExternalPtr<T>`.
11///
12/// This is a convenience function for ALTREP implementations that store
13/// their data in an `ExternalPtr` in the data1 slot.
14///
15/// # Safety
16///
17/// - `x` must be a valid ALTREP SEXP
18/// - Must be called from the R main thread
19///
20/// # Example
21///
22/// ```ignore
23/// impl Altrep for MyAltrepClass {
24///     const HAS_LENGTH: bool = true;
25///     fn length(x: SEXP) -> R_xlen_t {
26///         match unsafe { altrep_data1_as::<MyData>(x) } {
27///             Some(ext) => ext.data.len() as R_xlen_t,
28///             None => 0,
29///         }
30///     }
31/// }
32/// ```
33#[inline]
34pub unsafe fn altrep_data1_as<T: TypedExternal>(x: SEXP) -> Option<ExternalPtr<T>> {
35    unsafe { ExternalPtr::wrap_sexp(crate::ffi::R_altrep_data1(x)) }
36}
37
38/// Extract the ALTREP data1 slot (unchecked version).
39///
40/// Skips thread safety checks for performance-critical ALTREP callbacks.
41///
42/// # Safety
43///
44/// - `x` must be a valid ALTREP SEXP
45/// - Must be called from the R main thread (guaranteed in ALTREP callbacks)
46#[inline]
47pub unsafe fn altrep_data1_as_unchecked<T: TypedExternal>(x: SEXP) -> Option<ExternalPtr<T>> {
48    use crate::ffi::R_altrep_data1_unchecked;
49    unsafe { ExternalPtr::wrap_sexp_unchecked(R_altrep_data1_unchecked(x)) }
50}
51
52/// Extract the ALTREP data2 slot as a typed `ExternalPtr<T>`.
53///
54/// Similar to `altrep_data1_as`, but for the data2 slot.
55///
56/// # Safety
57///
58/// - `x` must be a valid ALTREP SEXP
59/// - Must be called from the R main thread
60#[inline]
61pub unsafe fn altrep_data2_as<T: TypedExternal>(x: SEXP) -> Option<ExternalPtr<T>> {
62    unsafe { ExternalPtr::wrap_sexp(crate::ffi::R_altrep_data2(x)) }
63}
64
65/// Extract the ALTREP data2 slot (unchecked version).
66///
67/// Skips thread safety checks for performance-critical ALTREP callbacks.
68///
69/// # Safety
70///
71/// - `x` must be a valid ALTREP SEXP
72/// - Must be called from the R main thread (guaranteed in ALTREP callbacks)
73#[inline]
74pub unsafe fn altrep_data2_as_unchecked<T: TypedExternal>(x: SEXP) -> Option<ExternalPtr<T>> {
75    use crate::ffi::R_altrep_data2_unchecked;
76    unsafe { ExternalPtr::wrap_sexp_unchecked(R_altrep_data2_unchecked(x)) }
77}
78
79/// Get a mutable reference to data in ALTREP data1 slot via `ErasedExternalPtr`.
80///
81/// This is useful for ALTREP methods that need to mutate the underlying data.
82///
83/// # Safety
84///
85/// - `x` must be a valid ALTREP SEXP
86/// - Must be called from the R main thread
87/// - The caller must ensure no other references to the data exist
88///
89/// # Example
90///
91/// ```ignore
92/// fn dataptr(x: SEXP, _writable: bool) -> *mut c_void {
93///     match unsafe { altrep_data1_mut::<MyData>(x) } {
94///         Some(data) => data.buffer.as_mut_ptr().cast(),
95///         None => core::ptr::null_mut(),
96///     }
97/// }
98/// ```
99#[inline]
100pub unsafe fn altrep_data1_mut<T: TypedExternal>(x: SEXP) -> Option<&'static mut T> {
101    unsafe {
102        let mut erased = ErasedExternalPtr::from_sexp(crate::ffi::R_altrep_data1(x));
103        // Transmute the lifetime to 'static - this is safe because:
104        // 1. The ExternalPtr is protected by R's GC as part of the ALTREP object
105        // 2. The ALTREP object `x` is kept alive by R during the callback
106        erased.downcast_mut::<T>().map(|r| std::mem::transmute(r))
107    }
108}
109
110/// Get a mutable reference to data in ALTREP data1 slot (unchecked version).
111///
112/// Skips thread safety checks for performance-critical ALTREP callbacks.
113///
114/// # Safety
115///
116/// - `x` must be a valid ALTREP SEXP
117/// - Must be called from the R main thread (guaranteed in ALTREP callbacks)
118/// - The caller must ensure no other references to the data exist
119#[inline]
120pub unsafe fn altrep_data1_mut_unchecked<T: TypedExternal>(x: SEXP) -> Option<&'static mut T> {
121    use crate::ffi::R_altrep_data1_unchecked;
122    unsafe {
123        let mut erased = ErasedExternalPtr::from_sexp(R_altrep_data1_unchecked(x));
124        erased.downcast_mut::<T>().map(|r| std::mem::transmute(r))
125    }
126}
127
128// Tests for ExternalPtr require R runtime, so they are in rpkg/src/rust/lib.rs
129// endregion
130
131// region: Sidecar Marker Type for #[r_data] Fields
132
133/// Marker type for enabling R sidecar accessors in an `ExternalPtr` struct.
134///
135/// When used with `#[derive(ExternalPtr)]` and `#[r_data]`, this field acts as
136/// a selector that enables R-facing accessors for sibling `#[r_data]` fields.
137///
138/// # Supported Field Types
139///
140/// - **`SEXP`** - Raw SEXP access, no conversion
141/// - **`i32`, `f64`, `bool`, `u8`** - Zero-overhead scalars (stored directly in R)
142/// - **Any `IntoR` type** - Automatic conversion (e.g., `String`, `Vec<T>`)
143///
144/// # Example
145///
146/// ```ignore
147/// use miniextendr_api::ffi::SEXP;
148///
149/// #[derive(ExternalPtr)]
150/// pub struct MyType {
151///     pub x: i32,
152///
153///     /// Selector field - enables R wrapper generation
154///     #[r_data]
155///     r: RSidecar,
156///
157///     /// Raw SEXP slot - MyType_get_raw() / MyType_set_raw()
158///     #[r_data]
159///     pub raw: SEXP,
160///
161///     /// Zero-overhead scalar - MyType_get_count() / MyType_set_count()
162///     #[r_data]
163///     pub count: i32,
164///
165///     /// Conversion type - MyType_get_name() / MyType_set_name()
166///     #[r_data]
167///     pub name: String,
168/// }
169/// ```
170///
171/// # Design Notes
172///
173/// - `RSidecar` is a ZST (zero-sized type) - no runtime cost
174/// - Only `pub` `#[r_data]` fields get R wrapper functions generated
175/// - Multiple `RSidecar` fields in one struct is a compile error
176#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
177pub struct RSidecar;
178// endregion