Skip to main content

miniextendr_api/altrep_data/
buffer.rs

1//! Native Vec ownership that preserves allocations already exposed to R.
2use super::{AltrepDataptr, AltrepLen, AltrepSerialize};
3use crate::{IntoR, RNativeType, SEXP, TryFromSexp};
4
5/// A growable native buffer whose exposed allocations survive later edits.
6///
7/// Wrap in `Altrep` to return it to R. Built-in classes use value semantics:
8/// R duplication clones the current values, so a copy R makes keeps its own
9/// values. An edit through the owner of an R object changes that object in
10/// place, for every binding R has of it.
11///
12/// [`edit`](Self::edit) detaches first if R has requested a data pointer, and
13/// keeps the old allocation until this owner is dropped. Previously returned
14/// pointers then refer to the prior values; subsequent callbacks see the new
15/// values. R provides no pointer-release notification, so repeated exposure
16/// followed by editing retains one buffer per such edit. No buffer is copied
17/// when it has only been read through `Elt` or `Get_region`.
18///
19/// This type deliberately has no `DerefMut` or method returning `&mut Vec<T>`:
20/// growth must pass through the edit boundary.
21#[derive(#[automatically_derived]
impl<T: ::core::fmt::Debug + RNativeType> ::core::fmt::Debug for
    AltrepBuffer<T> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f, "AltrepBuffer",
            "values", &self.values, "exposed", &self.exposed, "retired",
            &&self.retired)
    }
}Debug)]
22pub struct AltrepBuffer<T: RNativeType> {
23    values: Vec<T>,
24    exposed: bool,
25    retired: Vec<Vec<T>>,
26}
27
28impl<T: RNativeType> AltrepBuffer<T> {
29    /// Take ownership without copying or exposing the allocation.
30    pub fn new(values: Vec<T>) -> Self {
31        Self {
32            values,
33            exposed: false,
34            retired: Vec::new(),
35        }
36    }
37
38    /// Read the current values. The borrow prevents concurrent Rust edits.
39    pub fn as_slice(&self) -> &[T] {
40        &self.values
41    }
42
43    /// Edit the Vec, retaining its old allocation if a pointer was exposed.
44    /// A panic in `edit` leaves earlier pointers valid and keeps any changes
45    /// already made to the new Vec; it does not roll the edit back.
46    pub fn edit<R>(&mut self, edit: impl FnOnce(&mut Vec<T>) -> R) -> R {
47        if self.exposed {
48            let next = self.values.clone();
49            self.retired.push(std::mem::replace(&mut self.values, next));
50            self.exposed = false;
51        }
52        edit(&mut self.values)
53    }
54
55    /// Current number of elements.
56    pub fn len(&self) -> usize {
57        self.values.len()
58    }
59    /// Capacity of the current Vec, excluding retained older allocations.
60    pub fn capacity(&self) -> usize {
61        self.values.capacity()
62    }
63    /// Whether the current vector is empty.
64    pub fn is_empty(&self) -> bool {
65        self.values.is_empty()
66    }
67}
68
69impl<T: RNativeType> AsRef<[T]> for AltrepBuffer<T> {
70    fn as_ref(&self) -> &[T] {
71        self.as_slice()
72    }
73}
74
75impl<T: RNativeType> From<Vec<T>> for AltrepBuffer<T> {
76    fn from(values: Vec<T>) -> Self {
77        Self::new(values)
78    }
79}
80impl<T: RNativeType> Clone for AltrepBuffer<T> {
81    fn clone(&self) -> Self {
82        Self::new(self.values.clone())
83    }
84    fn clone_from(&mut self, source: &Self) {
85        self.edit(|values| values.clone_from(&source.values));
86    }
87}
88impl<T: RNativeType> AltrepLen for AltrepBuffer<T> {
89    fn altrep_len(&self) -> usize {
90        self.len()
91    }
92}
93impl<T: RNativeType> crate::externalptr::TypedExternal for AltrepBuffer<T> {
94    const TYPE_NAME: &'static str = "AltrepBuffer";
95    const TYPE_NAME_CSTR: &'static [u8] = b"AltrepBuffer\0";
96    const TYPE_ID_CSTR: &'static [u8] = b"miniextendr::AltrepBuffer\0";
97}
98impl<T: RNativeType> AltrepSerialize for AltrepBuffer<T>
99where
100    Vec<T>: IntoR + TryFromSexp,
101{
102    fn serialized_state(&self) -> SEXP {
103        self.values.clone().into_sexp()
104    }
105    fn unserialize(state: SEXP) -> Option<Self> {
106        Vec::<T>::try_from_sexp(state).ok().map(Self::new)
107    }
108}
109
110macro_rules! native_buffer {
111    ($element:ty, $storage:ty, $family:ident) => {
112        impl super::$family for AltrepBuffer<$element> {
113            fn elt(&self, i: usize) -> $storage {
114                self.values[i]
115            }
116            fn as_slice(&self) -> Option<&[$storage]> {
117                Some(&self.values)
118            }
119        }
120        impl AltrepDataptr<$storage> for AltrepBuffer<$element> {
121            fn dataptr(&mut self, _writable: bool) -> Option<*mut $storage> {
122                self.exposed = true;
123                Some(self.values.as_mut_ptr().cast())
124            }
125            // Use the mutable path even for full read-only Dataptr requests,
126            // preserving mutable pointer provenance. Pointer peeks stay lazy.
127        }
128    };
129}
130impl super::AltIntegerData for AltrepBuffer<i32> {
    fn elt(&self, i: usize) -> i32 { self.values[i] }
    fn as_slice(&self) -> Option<&[i32]> { Some(&self.values) }
}
impl AltrepDataptr<i32> for AltrepBuffer<i32> {
    fn dataptr(&mut self, _writable: bool) -> Option<*mut i32> {
        self.exposed = true;
        Some(self.values.as_mut_ptr().cast())
    }
}native_buffer!(i32, i32, AltIntegerData);
131impl super::AltRealData for AltrepBuffer<f64> {
    fn elt(&self, i: usize) -> f64 { self.values[i] }
    fn as_slice(&self) -> Option<&[f64]> { Some(&self.values) }
}
impl AltrepDataptr<f64> for AltrepBuffer<f64> {
    fn dataptr(&mut self, _writable: bool) -> Option<*mut f64> {
        self.exposed = true;
        Some(self.values.as_mut_ptr().cast())
    }
}native_buffer!(f64, f64, AltRealData);
132impl super::AltRawData for AltrepBuffer<u8> {
    fn elt(&self, i: usize) -> u8 { self.values[i] }
    fn as_slice(&self) -> Option<&[u8]> { Some(&self.values) }
}
impl AltrepDataptr<u8> for AltrepBuffer<u8> {
    fn dataptr(&mut self, _writable: bool) -> Option<*mut u8> {
        self.exposed = true;
        Some(self.values.as_mut_ptr().cast())
    }
}native_buffer!(u8, u8, AltRawData);
133impl super::AltComplexData for AltrepBuffer<crate::Rcomplex> {
    fn elt(&self, i: usize) -> crate::Rcomplex { self.values[i] }
    fn as_slice(&self) -> Option<&[crate::Rcomplex]> { Some(&self.values) }
}
impl AltrepDataptr<crate::Rcomplex> for AltrepBuffer<crate::Rcomplex> {
    fn dataptr(&mut self, _writable: bool) -> Option<*mut crate::Rcomplex> {
        self.exposed = true;
        Some(self.values.as_mut_ptr().cast())
    }
}native_buffer!(crate::Rcomplex, crate::Rcomplex, AltComplexData);
134
135impl super::AltLogicalData for AltrepBuffer<crate::RLogical> {
136    fn elt(&self, i: usize) -> super::Logical {
137        self.values[i].into()
138    }
139    fn as_r_slice(&self) -> Option<&[i32]> {
140        // RLogical has the same layout and valid bit patterns as i32.
141        Some(unsafe { std::slice::from_raw_parts(self.values.as_ptr().cast(), self.values.len()) })
142    }
143}
144impl AltrepDataptr<i32> for AltrepBuffer<crate::RLogical> {
145    fn dataptr(&mut self, _writable: bool) -> Option<*mut i32> {
146        self.exposed = true;
147        Some(self.values.as_mut_ptr().cast())
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154
155    #[test]
156    fn growth_replacement_and_panics_keep_exposed_allocations_alive() {
157        let mut values = AltrepBuffer::new(vec![1_i32, 2]);
158        let old = values.dataptr(true).unwrap();
159        values.edit(|v| {
160            v[0] = 3;
161            v.resize(4096, 9);
162        });
163        let current = values.dataptr(true).unwrap();
164        assert_ne!(old, current);
165        unsafe {
166            assert_eq!(*old, 1);
167            assert_eq!(*current, 3);
168        }
169        assert_eq!(values.len(), 4096);
170        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
171            values.edit(|v| {
172                v.clear();
173                panic!("interrupted edit");
174            });
175        }));
176        assert!(result.is_err());
177        unsafe {
178            assert_eq!(*old, 1);
179            assert_eq!(*current, 3);
180        }
181        assert!(values.is_empty());
182    }
183
184    #[test]
185    fn unexposed_edits_reuse_capacity_and_clones_detach() {
186        let mut values = AltrepBuffer::new(Vec::<i32>::with_capacity(8));
187        let before = values.values.as_ptr();
188        values.edit(|v| v.extend([1, 2, 3]));
189        assert_eq!(values.values.as_ptr(), before);
190        let exposed = values.dataptr(true).unwrap();
191        let mut copy = values.clone();
192        copy.edit(|v| v[0] = 7);
193        unsafe {
194            assert_eq!(*exposed, 1);
195        }
196        assert_eq!(copy.as_slice(), [7, 2, 3]);
197        assert!(copy.retired.is_empty());
198    }
199}