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::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///
98/// Takes no `RefCell` borrow: the ALTREP callbacks read their data unchecked
99/// until #2103 measures the checked read.
100#[inline]
101pub unsafe fn altrep_data1_mut<T: TypedExternal>(x: SEXP) -> Option<&'static mut T> {
102 unsafe { data1_value_mut(ErasedExternalPtr::from_sexp(SEXP::altrep_data1_raw(x))) }
103}
104
105/// Get a mutable reference to data in ALTREP data1 slot (unchecked version).
106///
107/// Skips thread safety checks for performance-critical ALTREP callbacks.
108///
109/// # Safety
110///
111/// - `x` must be a valid ALTREP SEXP
112/// - Must be called from the R main thread (guaranteed in ALTREP callbacks)
113/// - The caller must ensure no other references to the data exist
114#[inline]
115pub unsafe fn altrep_data1_mut_unchecked<T: TypedExternal>(x: SEXP) -> Option<&'static mut T> {
116 unsafe {
117 data1_value_mut(ErasedExternalPtr::from_sexp(
118 SEXP::altrep_data1_raw_unchecked(x),
119 ))
120 }
121}
122
123/// The `T` that `erased` holds, with no `RefCell` borrow.
124///
125/// # Safety
126///
127/// As for [`altrep_data1_mut`]. The `'static` lifetime is sound for the
128/// callback: R's GC protects the ALTREP object, and so its data1, while the
129/// callback runs.
130#[inline]
131unsafe fn data1_value_mut<T: TypedExternal>(erased: ErasedExternalPtr) -> Option<&'static mut T> {
132 erased
133 .downcast_cell::<T>()
134 // SAFETY: caller contract; `RefCell::as_ptr` may be written through.
135 .map(|cell| unsafe { &mut *cell.as_ptr() })
136}
137
138// Tests for ExternalPtr require R runtime, so they are in rpkg/src/rust/lib.rs
139// endregion
140
141// region: Sidecar Marker Type for #[r_data] Fields
142
143/// Marker type for enabling R sidecar accessors in an `ExternalPtr` struct.
144///
145/// When used with `#[derive(ExternalPtr)]` and `#[r_data]`, this field acts as
146/// a selector that enables R-facing accessors for sibling `#[r_data]` fields.
147///
148/// # Supported Field Types
149///
150/// - **[`Sidecar<T>`](super::Sidecar)** - a typed value kept in the external
151/// pointer's protection list (not in the struct), so the pointer roots it
152/// and `saveRDS()` writes it; Rust reads and writes it through the
153/// accessors the derive generates
154/// - **`i32`, `f64`, `bool`, `u8`** - scalars read with `Rf_as*` on write and
155/// returned as a fresh length-1 vector on read
156/// - **Any `IntoR + TryFromSexp` type** - converted on every read and write
157/// (e.g., `String`, `Vec<T>`)
158/// - **[`Computed`]** - a read-only field computed from the Rust value by a
159/// getter function (`#[r_data(get = "Self::f")]`)
160///
161/// Every field but a `Sidecar` lives in the Rust struct, so a reader always
162/// sees the Rust value and a writer either converts into it or gets an error.
163/// Those values sit behind the pointer's address, which `saveRDS` does not
164/// write. `Sidecar` values travel with the pointer.
165///
166/// # Example
167///
168/// ```ignore
169/// use miniextendr_api::externalptr::{RSidecar, Sidecar};
170///
171/// #[derive(ExternalPtr)]
172/// pub struct MyType {
173/// pub x: i32,
174///
175/// /// Selector field - enables R wrapper generation
176/// #[r_data]
177/// r: RSidecar,
178///
179/// /// Rooted value - MyType_get_keys() / MyType_set_keys(), and
180/// /// `MyType::keys(&ptr)` / `MyType::set_keys(&mut ptr, v)` in Rust
181/// #[r_data]
182/// pub keys: Sidecar<Vec<i32>>,
183///
184/// /// Zero-overhead scalar - MyType_get_count() / MyType_set_count()
185/// #[r_data]
186/// pub count: i32,
187///
188/// /// Conversion type - MyType_get_name() / MyType_set_name()
189/// #[r_data]
190/// pub name: String,
191/// }
192/// ```
193///
194/// # Design Notes
195///
196/// - `RSidecar` is a ZST (zero-sized type) - no runtime cost
197/// - Only `pub` `#[r_data]` fields get R wrapper functions generated
198/// - Multiple `RSidecar` fields in one struct is a compile error
199#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
200pub struct RSidecar;
201
202/// Marker type of a read-only R field computed from the Rust value
203/// (#1883).
204///
205/// Declare it as `#[r_data(get = "Self::f")] pub f: Computed` on a
206/// `#[derive(ExternalPtr)]` struct with an [`RSidecar`] selector. `get`
207/// names a plain Rust function `fn(&Self) -> T` with `T: IntoR`, resolved in
208/// the struct's module (a leading `Self` is the struct); it needs no
209/// `#[miniextendr]`. The field holds no value: it is a zero-sized marker that
210/// gives the computed field its place among the type's R fields, so every
211/// struct literal writes `f: Computed` once, as it writes `_r: RSidecar`.
212///
213/// ```ignore
214/// #[derive(ExternalPtr)]
215/// #[externalptr(s3)]
216/// pub struct Engine {
217/// inner: Model,
218/// #[r_data] _r: RSidecar,
219/// #[r_data(get = "Self::group_by")] pub group_by: Computed,
220/// #[r_data(name = "keys")] pub r_keys: Sidecar<SEXP>,
221/// #[r_data(get = "Self::n_rows")] pub n_rows: Computed,
222/// }
223///
224/// impl Engine {
225/// fn group_by(&self) -> Vec<String> { self.inner.group_by.clone() }
226/// fn n_rows(&self) -> i32 { self.inner.n_rows() }
227/// }
228/// ```
229///
230/// What the derive generates for it:
231///
232/// - the standalone getter `Engine_get_group_by(x)`, and an arm in the
233/// `$` / `[[` field methods of `s3(r_data_accessors)` & co., at the field's
234/// declared place among the `Sidecar` and struct fields;
235/// - no setter: no `Engine_set_group_by()`, and under the get/set form an
236/// assignment (`x$group_by <- v`) raises `miniextendr_read_only_field`.
237///
238/// The getter runs on R's main thread with the live Rust value, so a
239/// computed field refuses a pointer restored from a saved session like a
240/// struct field does. Its errors are its own: a `rust_error!(class = ...)`
241/// in the getter reaches R with that class. The getter returns a value, not a
242/// `Result`; it takes `&Self`, so it can't read `Sidecar` values (#1856),
243/// which are R fields already.
244///
245/// Only a `pub` `Computed` field reaches R, and only on S3, S4, env and
246/// vctrs types: R6 active bindings and S7 properties need a setter (#1888).
247#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
248pub struct Computed;
249// endregion