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