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