miniextendr_api/into_r/altrep.rs
1//! ALTREP marker type (`Altrep<T>` / `Lazy<T>`).
2//!
3//! Wrapping a value in `Altrep(value)` opts into ALTREP representation
4//! instead of eager copy when returned from a `#[miniextendr]` function.
5//!
6//! `Lazy<T>` is a type alias for `Altrep<T>` — use whichever reads better.
7//!
8//! # Tradeoff
9//!
10//! The bare `Vec<T>` impl in [`crate::into_r`] (eager copy) is the default —
11//! ALTREP is opt-in via this wrapper. Reach for `Altrep<Vec<T>>` when the
12//! vector is large and most callers index into a small slice; stay on the
13//! eager path for short vectors and anything R will scan end-to-end anyway
14//! (the ALTREP dispatch per element is not free). Failure mode of wrapping
15//! a tiny vector in `Altrep`: you pay per-element callback overhead for no
16//! benefit.
17//!
18//! For derive-driven ALTREP classes (rather than this on-demand wrapper),
19//! see the `#[derive(AltrepInteger)]` family.
20
21use crate::into_r::IntoR;
22
23/// Marker type to opt-in to ALTREP representation for types that have both
24/// eager-copy and ALTREP implementations.
25///
26/// The marker supports unsized views such as `&Altrep<[i32]>`. Converting a
27/// value into R still requires a sized owner with registered callbacks; use
28/// `Altrep<Box<[i32]>>` or `Box<Altrep<[i32]>>` for an owned slice.
29///
30/// # Motivation
31///
32/// Types like `Vec<i32>` have two possible conversions to R:
33/// 1. **Eager copy** (default): copies all data to R immediately
34/// 2. **ALTREP**: keeps data in Rust, provides it on-demand to R
35///
36/// The default `IntoR` for `Vec<i32>` does eager copy. To get ALTREP behavior,
37/// wrap your value in `Altrep<T>`.
38///
39/// # Example
40///
41/// ```ignore
42/// use miniextendr_api::{miniextendr, Altrep};
43///
44/// // Returns an ALTREP-backed integer vector (data stays in Rust)
45/// #[miniextendr]
46/// fn altrep_vec() -> Altrep<Vec<i32>> {
47/// Altrep((0..1_000_000).collect())
48/// }
49///
50/// // Returns a regular R vector (data copied to R)
51/// #[miniextendr]
52/// fn regular_vec() -> Vec<i32> {
53/// (0..1_000_000).collect()
54/// }
55/// ```
56///
57/// # Supported Types
58///
59/// `Altrep<T>` works with any type that implements both:
60/// - [`RegisterAltrep`](crate::altrep::RegisterAltrep) - for ALTREP class registration
61/// - [`TypedExternal`](crate::externalptr::TypedExternal) - for wrapping in ExternalPtr
62///
63/// Built-in supported types:
64/// - `Vec<i32>`, `Vec<f64>`, `Vec<bool>`, `Vec<u8>`, `Vec<String>`
65/// - `Box<[i32]>`, `Box<[f64]>`, `Box<[bool]>`, `Box<[u8]>`, `Box<[String]>`
66/// - `VecDeque<i32>`, `VecDeque<f64>`, `VecDeque<u8>`, `VecDeque<Rcomplex>`
67/// - `Rc<[T]>` / `Arc<[T]>` for `i32`, `f64`, `u8`, `Rcomplex`, `RLogical`, `bool`, `String`
68/// - `[T; N]` for `i32`, `f64`, `u8`, `Rcomplex`, `RLogical`, `bool`, `String`
69/// - `AltrepBuffer<T>` for native `i32`, `f64`, `u8`, `Rcomplex`, `RLogical`
70/// - `Range<i32>`, `Range<i64>`, `Range<f64>`
71/// - `Vec<RLogical>`, `Box<[RLogical]>`, `Cow<'static, [RLogical]>`
72/// - `Vec<Box<str>>` / `Vec<Rc<str>>` / `Vec<Arc<str>>`, also with optional elements
73///
74/// Opt-in lazy materialization via ALTREP.
75///
76/// Wrapping a return type in `Lazy<T>` causes it to be returned as an
77/// ALTREP vector backed by Rust-owned memory. R reads elements on demand;
78/// full materialization only happens if R needs a contiguous pointer.
79///
80/// # When to use
81/// - Large vectors (>1000 elements)
82/// - Data R may only partially read
83/// - Computed/external data (Arrow, ndarray, nalgebra)
84///
85/// # When NOT to use
86/// - Small vectors (<100 elements, ALTREP overhead dominates)
87/// - Data R will immediately modify (triggers instant materialization)
88///
89/// # Example
90/// ```rust,ignore
91/// #[miniextendr]
92/// fn big_result() -> Lazy<Vec<f64>> {
93/// Lazy(vec![0.0; 1_000_000])
94/// }
95/// ```
96pub type Lazy<T> = Altrep<T>;
97
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
99#[repr(transparent)]
100pub struct Altrep<T: ?Sized>(pub T);
101
102impl<T> Altrep<T> {
103 /// Create a new ALTREP marker wrapper.
104 #[inline]
105 pub fn new(value: T) -> Self {
106 Altrep(value)
107 }
108
109 /// Unwrap and return the inner value.
110 #[inline]
111 pub fn into_inner(self) -> T {
112 self.0
113 }
114
115 /// Convert to R ALTREP and wrap in [`AltrepSexp`](crate::altrep_sexp::AltrepSexp) (`!Send + !Sync`).
116 ///
117 /// This creates the ALTREP SEXP and wraps it in an `AltrepSexp` that
118 /// prevents the result from being sent to non-R threads. Use this when
119 /// you need to keep the ALTREP vector in Rust code and want compile-time
120 /// thread safety guarantees.
121 ///
122 /// For returning directly to R from `#[miniextendr]` functions, use
123 /// `Altrep<T>` as the return type (which implements `IntoR`) or call
124 /// `.into_sexp()` / `.into_sexp_altrep()` instead.
125 pub fn into_altrep_sexp(self) -> crate::altrep_sexp::AltrepSexp
126 where
127 T: crate::altrep::RegisterAltrep + crate::externalptr::TypedExternal,
128 {
129 let sexp = self.into_sexp();
130 // Safety: we just created an ALTREP SEXP via R_new_altrep
131 unsafe { crate::altrep_sexp::AltrepSexp::from_raw(sexp) }
132 }
133}
134
135impl<T: ?Sized> Altrep<T> {
136 /// Remove a boxed marker without copying or reallocating its contents.
137 ///
138 /// Slice lengths and trait-object metadata are preserved.
139 pub fn into_boxed_inner(self: Box<Self>) -> Box<T> {
140 let raw = Box::into_raw(self);
141 // SAFETY: Altrep is repr(transparent) with exactly one field. The field
142 // has the same allocation layout and metadata as the wrapper. Project
143 // the raw pointer to preserve mutable provenance; transfer ownership once.
144 unsafe { Box::from_raw(core::ptr::addr_of_mut!((*raw).0)) }
145 }
146}
147
148/// A boxed unsized marker uses the same owned backend as `Altrep<Box<T>>`.
149impl<T: ?Sized> IntoR for Box<Altrep<T>>
150where
151 Box<T>: crate::altrep::RegisterAltrep + crate::externalptr::TypedExternal,
152{
153 type Error = std::convert::Infallible;
154
155 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
156 Altrep(self.into_boxed_inner()).try_into_sexp()
157 }
158
159 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
160 unsafe { Altrep(self.into_boxed_inner()).try_into_sexp_unchecked() }
161 }
162}
163
164impl<T> From<T> for Altrep<T> {
165 #[inline]
166 fn from(value: T) -> Self {
167 Altrep(value)
168 }
169}
170
171impl<T: ?Sized> std::ops::Deref for Altrep<T> {
172 type Target = T;
173
174 #[inline]
175 fn deref(&self) -> &Self::Target {
176 &self.0
177 }
178}
179
180impl<T: ?Sized> std::ops::DerefMut for Altrep<T> {
181 #[inline]
182 fn deref_mut(&mut self) -> &mut Self::Target {
183 &mut self.0
184 }
185}
186
187/// Convert `Altrep<T>` to R using ALTREP representation.
188///
189/// This creates an ALTREP object where the data stays in Rust and is
190/// provided to R on-demand through ALTREP callbacks.
191impl<T> IntoR for Altrep<T>
192where
193 T: crate::altrep::RegisterAltrep + crate::externalptr::TypedExternal,
194{
195 type Error = std::convert::Infallible;
196 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
197 Ok(self.into_sexp())
198 }
199 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
200 Ok(unsafe { self.into_sexp_unchecked() })
201 }
202 fn into_sexp(self) -> crate::SEXP {
203 let cls = <T as crate::altrep::RegisterAltrep>::get_or_init_class();
204 let ext_ptr = crate::externalptr::ExternalPtr::new(self.0);
205 let data1 = ext_ptr.as_sexp();
206 // Protect data1 across new_altrep — it may allocate and trigger GC.
207 unsafe {
208 crate::sys::Rf_protect_unchecked(data1);
209 let out = cls.new_altrep(data1, crate::SEXP::nil());
210 crate::sys::Rf_unprotect_unchecked(1);
211 out
212 }
213 }
214 unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
215 let cls = <T as crate::altrep::RegisterAltrep>::get_or_init_class();
216 let ext_ptr = crate::externalptr::ExternalPtr::new(self.0);
217 let data1 = ext_ptr.as_sexp();
218 unsafe {
219 crate::sys::Rf_protect_unchecked(data1);
220 let out = cls.new_altrep_unchecked(data1, crate::SEXP::nil());
221 crate::sys::Rf_unprotect_unchecked(1);
222 out
223 }
224 }
225}
226
227/// Convert `AltrepSexp` to R by returning the inner SEXP.
228///
229/// This allows `AltrepSexp` to be used as a return type from `#[miniextendr]`
230/// functions, transparently passing the ALTREP SEXP back to R.
231impl IntoR for crate::altrep_sexp::AltrepSexp {
232 type Error = std::convert::Infallible;
233 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
234 Ok(self.into_sexp())
235 }
236 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
237 self.try_into_sexp()
238 }
239 fn into_sexp(self) -> crate::SEXP {
240 // Safety: returning to R which is always the main thread context
241 unsafe { self.as_raw() }
242 }
243}
244// endregion
245
246#[cfg(test)]
247mod tests {
248 use super::Altrep;
249
250 #[test]
251 fn array_marker_coerces_to_mutable_slice_view() {
252 let mut array = Altrep([1, 2, 3]);
253 let slice: &mut Altrep<[i32]> = &mut array;
254 slice[1] = 9;
255 assert_eq!(&**slice, &[1, 9, 3]);
256 assert_eq!(array.into_inner(), [1, 9, 3]);
257 }
258}