Skip to main content

miniextendr_api/
rcow.rs

1//! [`RCow`] — an R-aware copy-on-write slice.
2//!
3//! Like [`std::borrow::Cow<[T]>`](std::borrow::Cow), but the borrowed arm
4//! carries the *source SEXP* it was read from. That single extra field is what
5//! makes a zero-copy round-trip back to R **sound**, where `Cow<[T]>` could not
6//! be (see #880).
7//!
8//! # The `Cow<[T]>` hazard this replaces
9//!
10//! `Cow<[T]>` is zero-copy on the way *in* through `TryFromRoot`, which returns
11//! `Cow::Borrowed(&[T])` pointing straight at R's vector data. But it cannot be
12//! returned to R zero-copy *safely*. A bare `&[T]` carries no provenance, so a
13//! borrowed **sub-slice** (`&cow[2..5]`) is byte-for-byte indistinguishable from
14//! a full borrow, yet `as_ptr()` points into the *middle* of the R vector. The
15//! old recovery path computed `data_ptr − SEXPREC_header` and speculatively read
16//! there — landing mid-header for a sub-slice (false-positive corruption) or off
17//! the front of a Rust-allocated buffer entirely (segfault — the same class of
18//! bug fixed for Arrow in #867). Arrow could keep zero-copy because
19//! `arrow::Buffer` *proves* it is unsliced and R-backed (`ptr_offset`/
20//! `capacity`); a `&[T]` proves nothing.
21//!
22//! # How `RCow` fixes it structurally
23//!
24//! [`RCow::Borrowed`] wraps [`RBorrow`], whose fields are private. The *only*
25//! constructor is [`RCow::try_from_root`], which always borrows a whole vector.
26//! There is no way to build a borrowed `RCow` over a sub-range (slice via
27//! [`Deref`] to get a plain `&[T]` instead). So a borrowed `RCow` always spans
28//! its entire source vector, and [`IntoR`] simply returns the stored SEXP — no
29//! pointer arithmetic, no speculative read, no hazard. Need to mutate or reshape?
30//! [`to_mut`](RCow::to_mut) / [`into_owned`](RCow::into_owned) copy out into an
31//! owned [`Vec<T>`].
32//!
33//! # Lifetime contract
34//!
35//! A borrowed `RCow` is tied to the lifetime-carrying [`Root`](crate::Root)
36//! passed to `try_from_root`. Generated wrappers borrow that root from the
37//! live `.Call` argument slot. Write `RCow<'_, T>`: a `'static` parameter
38//! would require the call's local root to live forever and is rejected.
39//! Raw `TryFromSexp` is unavailable for `RCow`; callers outside wrappers use
40//! a [`ProtectScope`](crate::ProtectScope) root.
41
42use std::ops::Deref;
43
44use crate::from_r::{SexpTypeError, TryFromSexp};
45use crate::into_r::IntoR;
46use crate::{RNativeType, SEXP};
47
48/// Borrowed arm of [`RCow`]: a whole-vector view that remembers its source SEXP.
49///
50/// Fields are private by design — the only constructor is
51/// [`RCow::try_from_root`], so a borrowed view can never be a sub-slice. That
52/// invariant is what lets [`IntoR`] return the source SEXP zero-copy without the
53/// provenance-free pointer probe that `Cow<[T]>` required (#880).
54pub struct RBorrow<'a, T> {
55    /// Source R vector. Valid for the duration of the enclosing `.Call`.
56    sexp: SEXP,
57    /// View over `sexp`'s data — always the whole vector (see invariant above).
58    data: &'a [T],
59}
60
61impl<T> RBorrow<'_, T> {
62    /// The borrowed view (the whole source vector).
63    #[inline]
64    pub fn as_slice(&self) -> &[T] {
65        self.data
66    }
67
68    /// The source R vector this view borrows from.
69    #[inline]
70    pub fn source_sexp(&self) -> SEXP {
71        self.sexp
72    }
73}
74
75/// An R-aware copy-on-write slice — the safe, zero-copy-round-trip alternative
76/// to [`std::borrow::Cow<[T]>`](std::borrow::Cow).
77///
78/// See the [module docs](self) for why this exists and how it closes the #880
79/// hazard. In brief: the [`Borrowed`](RCow::Borrowed) arm carries its source
80/// SEXP, so returning it to R is a direct hand-back rather than a speculative
81/// pointer recovery.
82///
83/// # Example
84///
85/// ```ignore
86/// // Zero-copy in *and* out: the returned SEXP is the original R vector.
87/// #[miniextendr]
88/// pub fn passthrough(x: RCow<'_, f64>) -> RCow<'_, f64> {
89///     x
90/// }
91///
92/// // Mutating forces a copy (copy-on-write), then materializes a fresh vector.
93/// #[miniextendr]
94/// pub fn doubled(mut x: RCow<'_, f64>) -> RCow<'_, f64> {
95///     for v in x.to_mut() {
96///         *v *= 2.0;
97///     }
98///     x
99/// }
100/// ```
101pub enum RCow<'a, T> {
102    /// Zero-copy view of a whole R vector, carrying its source SEXP.
103    Borrowed(RBorrow<'a, T>),
104    /// Owned data; materializes a fresh R vector on [`IntoR`].
105    Owned(Vec<T>),
106}
107
108impl<T> RCow<'_, T> {
109    /// `true` if this is a borrowed (zero-copy) view of an R vector.
110    #[inline]
111    pub fn is_borrowed(&self) -> bool {
112        #[allow(non_exhaustive_omitted_patterns)] match self {
    RCow::Borrowed(_) => true,
    _ => false,
}matches!(self, RCow::Borrowed(_))
113    }
114
115    /// `true` if this owns its data.
116    #[inline]
117    pub fn is_owned(&self) -> bool {
118        #[allow(non_exhaustive_omitted_patterns)] match self {
    RCow::Owned(_) => true,
    _ => false,
}matches!(self, RCow::Owned(_))
119    }
120}
121
122impl<T: Clone> RCow<'_, T> {
123    /// Acquire a mutable reference to the owned data, cloning out of R first if
124    /// borrowed (copy-on-write). After this the `RCow` is always
125    /// [`Owned`](RCow::Owned).
126    pub fn to_mut(&mut self) -> &mut Vec<T> {
127        match self {
128            RCow::Borrowed(b) => {
129                *self = RCow::Owned(b.data.to_vec());
130                match self {
131                    RCow::Owned(v) => v,
132                    // The line above just assigned `Owned`.
133                    RCow::Borrowed(_) => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
134                }
135            }
136            RCow::Owned(v) => v,
137        }
138    }
139
140    /// Consume into an owned [`Vec<T>`], cloning out of R if borrowed.
141    pub fn into_owned(self) -> Vec<T> {
142        match self {
143            RCow::Borrowed(b) => b.data.to_vec(),
144            RCow::Owned(v) => v,
145        }
146    }
147}
148
149impl<T> Deref for RCow<'_, T> {
150    type Target = [T];
151    #[inline]
152    fn deref(&self) -> &[T] {
153        match self {
154            RCow::Borrowed(b) => b.data,
155            RCow::Owned(v) => v,
156        }
157    }
158}
159
160impl<T> From<Vec<T>> for RCow<'_, T> {
161    #[inline]
162    fn from(v: Vec<T>) -> Self {
163        RCow::Owned(v)
164    }
165}
166
167impl<'a, T: RNativeType> RCow<'a, T> {
168    /// Native storage retained by a borrowed wrapper argument.
169    #[doc(hidden)]
170    pub const NATIVE_BORROW: Option<crate::from_r::NativeBorrow> =
171        <&[T] as TryFromSexp>::NATIVE_BORROW;
172
173    /// Borrow an entire native vector for the lifetime of its root.
174    ///
175    /// Type checking is the same as for `&[T]`. Retaining the source SEXP
176    /// preserves zero-copy round-trip identity; retaining the root's lifetime
177    /// prevents the borrowed value escaping that root.
178    ///
179    /// ```compile_fail
180    /// use miniextendr_api::{Root, RCow};
181    /// fn escape<'a>(root: Root<'a>) -> RCow<'static, i32> {
182    ///     RCow::try_from_root(root).unwrap()
183    /// }
184    /// ```
185    #[inline]
186    pub fn try_from_root(root: crate::Root<'a>) -> Result<Self, SexpTypeError> {
187        let sexp = root.get();
188        // The root keeps the source reachable for `'a`, which bounds the
189        // slice; the conversion checks the element type.
190        let data: &'a [T] = TryFromSexp::try_from_sexp(sexp)?;
191        Ok(RCow::Borrowed(RBorrow { sexp, data }))
192    }
193}
194
195/// Returns the source SEXP unchanged for the borrowed arm (true zero-copy), or
196/// materializes a fresh R vector for the owned arm.
197impl<T: RNativeType> IntoR for RCow<'_, T> {
198    type Error = std::convert::Infallible;
199
200    #[inline]
201    fn try_into_sexp(self) -> Result<SEXP, Self::Error> {
202        Ok(self.into_sexp())
203    }
204
205    #[inline]
206    unsafe fn try_into_sexp_unchecked(self) -> Result<SEXP, Self::Error> {
207        Ok(unsafe { self.into_sexp_unchecked() })
208    }
209
210    #[inline]
211    fn into_sexp(self) -> SEXP {
212        match self {
213            // Invariant (enforced by `RBorrow`'s private fields): a borrowed
214            // `RCow` always spans its entire source vector, so the source SEXP
215            // *is* the correct result — hand it back directly. No
216            // `data_ptr − header` probe, hence none of the #880 / #867
217            // speculative-read hazard.
218            RCow::Borrowed(b) => b.sexp,
219            // Copies via the `&[T]` impl.
220            RCow::Owned(v) => v.as_slice().into_sexp(),
221        }
222    }
223
224    #[inline]
225    unsafe fn into_sexp_unchecked(self) -> SEXP {
226        match self {
227            RCow::Borrowed(b) => b.sexp,
228            RCow::Owned(v) => unsafe { v.as_slice().into_sexp_unchecked() },
229        }
230    }
231}
232
233#[cfg(test)]
234mod tests {
235    use super::*;
236
237    // The borrowed arm's behavior (try_from_root / zero-copy IntoR) requires a
238    // live R session and is exercised by the rpkg `zero_copy_rcow_*` fixtures
239    // and the `gc_stress_rcow_roundtrip` gctorture guard. These unit tests cover
240    // the R-independent surface (the owned arm and the Cow-like helpers).
241
242    #[test]
243    fn owned_deref_and_into_owned() {
244        let c: RCow<'static, i32> = RCow::Owned(vec![1, 2, 3]);
245        assert!(c.is_owned());
246        assert!(!c.is_borrowed());
247        assert_eq!(&*c, &[1, 2, 3]);
248        assert_eq!(c.into_owned(), vec![1, 2, 3]);
249    }
250
251    #[test]
252    fn to_mut_on_owned_mutates_in_place() {
253        let mut c: RCow<'static, f64> = RCow::Owned(vec![1.0]);
254        c.to_mut().push(2.0);
255        assert_eq!(&*c, &[1.0, 2.0]);
256    }
257
258    #[test]
259    fn from_vec_is_owned() {
260        let c: RCow<'static, i32> = vec![9, 8, 7].into();
261        assert!(c.is_owned());
262        assert_eq!(c.len(), 3);
263    }
264}