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/// 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(#[automatically_derived]
impl<T: ::core::fmt::Debug + ?Sized> ::core::fmt::Debug for Altrep<T> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Altrep",
            &&self.0)
    }
}Debug, #[automatically_derived]
impl<T: ::core::clone::Clone + ?Sized> ::core::clone::Clone for Altrep<T> {
    #[inline]
    fn clone(&self) -> Self { Altrep(::core::clone::Clone::clone(&self.0)) }
}Clone, #[automatically_derived]
impl<T: ::core::marker::Copy + ?Sized> ::core::marker::Copy for Altrep<T> { }Copy, #[automatically_derived]
impl<T: ::core::cmp::PartialEq + ?Sized> ::core::marker::StructuralPartialEq
    for Altrep<T> {
}
#[automatically_derived]
impl<T: ::core::cmp::PartialEq + ?Sized> ::core::cmp::PartialEq for Altrep<T>
    {
    #[inline]
    fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}PartialEq, #[automatically_derived]
impl<T: ::core::cmp::Eq + ?Sized> ::core::cmp::Eq for Altrep<T> {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) { let _: ::core::cmp::AssertParamIsEq<T>; }
}Eq, #[automatically_derived]
impl<T: ::core::hash::Hash + ?Sized> ::core::hash::Hash for Altrep<T> {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.0, state)
    }
}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(&raw mut (*raw).0core::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}