Skip to main content

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