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/// # Motivation
27///
28/// Types like `Vec<i32>` have two possible conversions to R:
29/// 1. **Eager copy** (default): copies all data to R immediately
30/// 2. **ALTREP**: keeps data in Rust, provides it on-demand to R
31///
32/// The default `IntoR` for `Vec<i32>` does eager copy. To get ALTREP behavior,
33/// wrap your value in `Altrep<T>`.
34///
35/// # Example
36///
37/// ```ignore
38/// use miniextendr_api::{miniextendr, Altrep};
39///
40/// // Returns an ALTREP-backed integer vector (data stays in Rust)
41/// #[miniextendr]
42/// fn altrep_vec() -> Altrep<Vec<i32>> {
43/// Altrep((0..1_000_000).collect())
44/// }
45///
46/// // Returns a regular R vector (data copied to R)
47/// #[miniextendr]
48/// fn regular_vec() -> Vec<i32> {
49/// (0..1_000_000).collect()
50/// }
51/// ```
52///
53/// # Supported Types
54///
55/// `Altrep<T>` works with any type that implements both:
56/// - [`RegisterAltrep`](crate::altrep::RegisterAltrep) - for ALTREP class registration
57/// - [`TypedExternal`](crate::externalptr::TypedExternal) - for wrapping in ExternalPtr
58///
59/// Built-in supported types:
60/// - `Vec<i32>`, `Vec<f64>`, `Vec<bool>`, `Vec<u8>`, `Vec<String>`
61/// - `Box<[i32]>`, `Box<[f64]>`, `Box<[bool]>`, `Box<[u8]>`, `Box<[String]>`
62/// - `Range<i32>`, `Range<i64>`, `Range<f64>`
63///
64/// Opt-in lazy materialization via ALTREP.
65///
66/// Wrapping a return type in `Lazy<T>` causes it to be returned as an
67/// ALTREP vector backed by Rust-owned memory. R reads elements on demand;
68/// full materialization only happens if R needs a contiguous pointer.
69///
70/// # When to use
71/// - Large vectors (>1000 elements)
72/// - Data R may only partially read
73/// - Computed/external data (Arrow, ndarray, nalgebra)
74///
75/// # When NOT to use
76/// - Small vectors (<100 elements, ALTREP overhead dominates)
77/// - Data R will immediately modify (triggers instant materialization)
78///
79/// # Example
80/// ```rust,ignore
81/// #[miniextendr]
82/// fn big_result() -> Lazy<Vec<f64>> {
83/// Lazy(vec![0.0; 1_000_000])
84/// }
85/// ```
86pub type Lazy<T> = Altrep<T>;
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
89#[repr(transparent)]
90pub struct Altrep<T>(pub T);
91
92impl<T> Altrep<T> {
93 /// Create a new ALTREP marker wrapper.
94 #[inline]
95 pub fn new(value: T) -> Self {
96 Altrep(value)
97 }
98
99 /// Unwrap and return the inner value.
100 #[inline]
101 pub fn into_inner(self) -> T {
102 self.0
103 }
104
105 /// Convert to R ALTREP and wrap in [`AltrepSexp`](crate::altrep_sexp::AltrepSexp) (`!Send + !Sync`).
106 ///
107 /// This creates the ALTREP SEXP and wraps it in an `AltrepSexp` that
108 /// prevents the result from being sent to non-R threads. Use this when
109 /// you need to keep the ALTREP vector in Rust code and want compile-time
110 /// thread safety guarantees.
111 ///
112 /// For returning directly to R from `#[miniextendr]` functions, use
113 /// `Altrep<T>` as the return type (which implements `IntoR`) or call
114 /// `.into_sexp()` / `.into_sexp_altrep()` instead.
115 pub fn into_altrep_sexp(self) -> crate::altrep_sexp::AltrepSexp
116 where
117 T: crate::altrep::RegisterAltrep + crate::externalptr::TypedExternal,
118 {
119 let sexp = self.into_sexp();
120 // Safety: we just created an ALTREP SEXP via R_new_altrep
121 unsafe { crate::altrep_sexp::AltrepSexp::from_raw(sexp) }
122 }
123}
124
125impl<T> From<T> for Altrep<T> {
126 #[inline]
127 fn from(value: T) -> Self {
128 Altrep(value)
129 }
130}
131
132impl<T> std::ops::Deref for Altrep<T> {
133 type Target = T;
134
135 #[inline]
136 fn deref(&self) -> &Self::Target {
137 &self.0
138 }
139}
140
141impl<T> std::ops::DerefMut for Altrep<T> {
142 #[inline]
143 fn deref_mut(&mut self) -> &mut Self::Target {
144 &mut self.0
145 }
146}
147
148/// Convert `Altrep<T>` to R using ALTREP representation.
149///
150/// This creates an ALTREP object where the data stays in Rust and is
151/// provided to R on-demand through ALTREP callbacks.
152impl<T> IntoR for Altrep<T>
153where
154 T: crate::altrep::RegisterAltrep + crate::externalptr::TypedExternal,
155{
156 type Error = std::convert::Infallible;
157 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
158 Ok(self.into_sexp())
159 }
160 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
161 Ok(unsafe { self.into_sexp_unchecked() })
162 }
163 fn into_sexp(self) -> crate::SEXP {
164 let cls = <T as crate::altrep::RegisterAltrep>::get_or_init_class();
165 let ext_ptr = crate::externalptr::ExternalPtr::new(self.0);
166 let data1 = ext_ptr.as_sexp();
167 // Protect data1 across new_altrep — it may allocate and trigger GC.
168 unsafe {
169 crate::sys::Rf_protect_unchecked(data1);
170 let out = cls.new_altrep(data1, crate::SEXP::nil());
171 crate::sys::Rf_unprotect_unchecked(1);
172 out
173 }
174 }
175 unsafe fn into_sexp_unchecked(self) -> crate::SEXP {
176 let cls = <T as crate::altrep::RegisterAltrep>::get_or_init_class();
177 let ext_ptr = crate::externalptr::ExternalPtr::new(self.0);
178 let data1 = ext_ptr.as_sexp();
179 unsafe {
180 crate::sys::Rf_protect_unchecked(data1);
181 let out = cls.new_altrep_unchecked(data1, crate::SEXP::nil());
182 crate::sys::Rf_unprotect_unchecked(1);
183 out
184 }
185 }
186}
187
188/// Convert `AltrepSexp` to R by returning the inner SEXP.
189///
190/// This allows `AltrepSexp` to be used as a return type from `#[miniextendr]`
191/// functions, transparently passing the ALTREP SEXP back to R.
192impl IntoR for crate::altrep_sexp::AltrepSexp {
193 type Error = std::convert::Infallible;
194 fn try_into_sexp(self) -> Result<crate::SEXP, Self::Error> {
195 Ok(self.into_sexp())
196 }
197 unsafe fn try_into_sexp_unchecked(self) -> Result<crate::SEXP, Self::Error> {
198 self.try_into_sexp()
199 }
200 fn into_sexp(self) -> crate::SEXP {
201 // Safety: returning to R which is always the main thread context
202 unsafe { self.as_raw() }
203 }
204}
205// endregion