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//!
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