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::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(#[automatically_derived]
impl ::core::fmt::Debug for RSidecar {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f, "RSidecar")
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for RSidecar { }
#[automatically_derived]
impl ::core::clone::Clone for RSidecar {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for RSidecar { }Copy, #[automatically_derived]
impl ::core::default::Default for RSidecar {
    #[inline]
    fn default() -> Self { RSidecar {} }
}Default, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for RSidecar { }
#[automatically_derived]
impl ::core::cmp::PartialEq for RSidecar {
    #[inline]
    fn eq(&self, other: &Self) -> bool { true }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for RSidecar { }Eq, #[automatically_derived]
impl ::core::hash::Hash for RSidecar {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {}
}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(#[automatically_derived]
impl ::core::fmt::Debug for Computed {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f, "Computed")
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Computed { }
#[automatically_derived]
impl ::core::clone::Clone for Computed {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Computed { }Copy, #[automatically_derived]
impl ::core::default::Default for Computed {
    #[inline]
    fn default() -> Self { Computed {} }
}Default, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for Computed { }
#[automatically_derived]
impl ::core::cmp::PartialEq for Computed {
    #[inline]
    fn eq(&self, other: &Self) -> bool { true }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Computed { }Eq, #[automatically_derived]
impl ::core::hash::Hash for Computed {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {}
}Hash)]
248pub struct Computed;
249// endregion