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