Skip to main content

extendr_api/wrapper/
externalptr.rs

1//! `ExternalPtr` is a way to leak Rust allocated data to R, forego deallocation
2//! to R and its GC strategy.
3//!
4//! An `ExternalPtr` encompasses three values, an owned pointer to the Rust
5//! type, a `tag` and a `prot`. Tag is a helpful naming of the type, but
6//! it doesn't offer any solid type-checking capability. And `prot` is meant
7//! to be R values, that are supposed to be kept together with the `ExternalPtr`.
8//!
9//! Neither `tag` nor `prot` are attributes, therefore to use `ExternalPtr` as
10//! a class in R, you must decorate it with a class-attribute manually.
11//!
12//! **Beware**: Equality (by way of `PartialEq`) does not imply equality of value,
13//! but equality of pointer. Two objects stored as `ExternalPtr` may be equal
14//! in value, but be two distinct entities, with distinct pointers.
15//!
16//! Instead, rely on `AsRef` to make _by value_ comparison, e.g. to compare
17//! for equality of
18//! two instances of `ExternalPtr<T>` by value, `a.as_ref() == b.as_ref()`.
19//!
20use super::*;
21use extendr_ffi::{
22    R_ClearExternalPtr, R_ExternalPtrAddr, R_ExternalPtrProtected, R_ExternalPtrTag,
23    R_MakeExternalPtr, R_NilValue, R_SetExternalPtrTag,
24};
25use std::{any::Any, fmt::Debug};
26/// Wrapper for creating R objects containing any Rust object.
27///
28/// ```
29/// use extendr_api::prelude::*;
30/// test! {
31///     let extptr = ExternalPtr::new(1);
32///     assert_eq!(*extptr, 1);
33///     let robj : Robj = extptr.into();
34///     let extptr2 : ExternalPtr<i32> = robj.try_into().unwrap();
35///     assert_eq!(*extptr2, 1);
36/// }
37/// ```
38#[repr(transparent)]
39pub struct ExternalPtr<T> {
40    /// This is the contained Robj.
41    pub(crate) robj: Robj,
42
43    /// This is a zero-length object that holds the type of the object.
44    _marker: std::marker::PhantomData<T>,
45}
46
47/// Manual implementation of `PartialEq`, because the constraint `T: PartialEq`
48/// is not necessary.
49impl<T> PartialEq for ExternalPtr<T> {
50    fn eq(&self, other: &Self) -> bool {
51        self.robj == other.robj && self._marker == other._marker
52    }
53}
54
55/// Manual implementation of `Clone` trait, because the assumed constraint `T: Clone` is not necessary.
56impl<T> Clone for ExternalPtr<T> {
57    fn clone(&self) -> Self {
58        Self {
59            robj: self.robj.clone(),
60            _marker: self._marker,
61        }
62    }
63}
64
65impl<T> robj::GetSexp for ExternalPtr<T> {
66    unsafe fn get(&self) -> SEXP {
67        self.robj.get()
68    }
69
70    unsafe fn get_mut(&mut self) -> SEXP {
71        self.robj.get_mut()
72    }
73
74    /// Get a reference to a Robj for this type.
75    fn as_robj(&self) -> &Robj {
76        &self.robj
77    }
78
79    /// Get a mutable reference to a Robj for this type.
80    fn as_robj_mut(&mut self) -> &mut Robj {
81        &mut self.robj
82    }
83}
84
85/// len() and is_empty()
86impl<T> Length for ExternalPtr<T> {}
87
88/// rtype() and rany()
89impl<T> Types for ExternalPtr<T> {}
90
91/// `set_attrib`
92impl<T> Attributes for ExternalPtr<T> {}
93
94/// as_*()
95impl<T> Conversions for ExternalPtr<T> {}
96
97/// find_var() etc.
98impl<T> Rinternals for ExternalPtr<T> {}
99
100/// as_typed_slice_raw() etc.
101impl<T> Slices for ExternalPtr<T> {}
102
103/// dollar() etc.
104impl<T> Operators for ExternalPtr<T> {}
105
106impl<T: 'static> Deref for ExternalPtr<T> {
107    type Target = T;
108
109    /// This allows us to treat the Robj as if it is the type T.
110    fn deref(&self) -> &Self::Target {
111        self.addr()
112    }
113}
114
115impl<T: 'static> DerefMut for ExternalPtr<T> {
116    /// This allows us to treat the Robj as if it is the mutable type T.
117    fn deref_mut(&mut self) -> &mut Self::Target {
118        self.addr_mut()
119    }
120}
121
122impl<T: 'static> ExternalPtr<T> {
123    /// Construct an external pointer object from any type T.
124    /// In this case, the R object owns the data and will drop the Rust object
125    /// when the last reference is removed via register_c_finalizer.
126    ///
127    /// An ExternalPtr behaves like a Box except that the information is
128    /// tracked by a R object.
129    pub fn new(val: T) -> Self {
130        single_threaded(|| unsafe {
131            // This allocates some memory for our object and moves the object into it.
132            let boxed: Box<dyn Any> = Box::new(val);
133            let boxed: Box<Box<dyn Any>> = Box::new(boxed);
134
135            // This constructs an external pointer to our boxed data.
136            // into_raw() converts the box to a malloced pointer.
137            let robj = {
138                let boxed_ptr = Box::into_raw(boxed);
139                let prot = Robj::null();
140                let type_name: Robj = std::any::type_name::<T>().into();
141
142                Robj::from_sexp(single_threaded(|| {
143                    R_MakeExternalPtr(boxed_ptr.cast(), type_name.get(), prot.get())
144                }))
145            };
146
147            extern "C" fn finalizer(x: SEXP) {
148                unsafe {
149                    let ptr = R_ExternalPtrAddr(x).cast::<Box<dyn Any>>();
150
151                    // Free the `tag`, which is the type-name
152                    R_SetExternalPtrTag(x, R_NilValue);
153
154                    // Convert the pointer to a box and drop it implictly.
155                    // This frees up the memory we have used and calls the "T::drop" method if there is one.
156                    drop(Box::from_raw(ptr));
157
158                    // Now set the pointer in ExternalPTR to C `NULL`
159                    R_ClearExternalPtr(x);
160                }
161            }
162
163            // Tell R about our finalizer
164            robj.register_c_finalizer(Some(finalizer));
165
166            // Return an object in a wrapper.
167            Self {
168                robj,
169                _marker: std::marker::PhantomData,
170            }
171        })
172    }
173
174    // TODO: make a constructor for references?
175
176    /// Get the "tag" of an external pointer. This is the type name in the common case.
177    pub fn tag(&self) -> Robj {
178        unsafe { Robj::from_sexp(R_ExternalPtrTag(self.robj.get())) }
179    }
180
181    /// Get the "protected" field of an external pointer. This is NULL in the common case.
182    pub fn protected(&self) -> Robj {
183        unsafe { Robj::from_sexp(R_ExternalPtrProtected(self.robj.get())) }
184    }
185
186    /// Get the "address" field of an external pointer.
187    /// Normally, we will use Deref to do this.
188    ///
189    /// ## Panics
190    ///
191    /// When the underlying pointer is C `NULL`.
192    pub fn addr(&self) -> &T {
193        self.try_addr().unwrap()
194    }
195
196    /// Get the "address" field of an external pointer as a mutable reference.
197    /// Normally, we will use DerefMut to do this.
198    ///
199    /// ## Panics
200    ///
201    /// When the underlying pointer is C `NULL`.
202    pub fn addr_mut(&mut self) -> &mut T {
203        self.try_addr_mut().unwrap()
204    }
205
206    /// Get the "address" field of an external pointer.
207    /// Normally, we will use Deref to do this.
208    ///
209    /// ## Errors
210    ///
211    /// When the underlying pointer is C `NULL`, or does not point to a `T`.
212    pub fn try_addr(&self) -> Result<&T> {
213        unsafe {
214            R_ExternalPtrAddr(self.robj.get())
215                .cast::<Box<dyn Any>>()
216                .as_ref()
217                .ok_or_else(|| Error::ExpectedExternalNonNullPtr(self.robj.clone()))
218                .and_then(|x| {
219                    x.downcast_ref::<T>().ok_or_else(|| {
220                        Error::ExpectedExternalPtrType(
221                            self.robj.clone(),
222                            std::any::type_name::<T>().to_string(),
223                        )
224                    })
225                })
226        }
227    }
228
229    /// Get the "address" field of an external pointer as a mutable reference.
230    /// Normally, we will use DerefMut to do this.
231    ///
232    /// ## Errors
233    ///
234    /// When the underlying pointer is C `NULL`, or does not point to a `T`.
235    pub fn try_addr_mut(&mut self) -> Result<&mut T> {
236        unsafe {
237            R_ExternalPtrAddr(self.robj.get_mut())
238                .cast::<Box<dyn Any>>()
239                .as_mut()
240                .ok_or_else(|| Error::ExpectedExternalNonNullPtr(self.robj.clone()))
241                .and_then(|x| {
242                    x.downcast_mut::<T>().ok_or_else(|| {
243                        Error::ExpectedExternalPtrType(
244                            self.robj.clone(),
245                            std::any::type_name::<T>().to_string(),
246                        )
247                    })
248                })
249        }
250    }
251}
252
253impl<T: 'static> TryFrom<&Robj> for &ExternalPtr<T> {
254    type Error = Error;
255
256    fn try_from(value: &Robj) -> Result<Self> {
257        if !value.is_external_pointer() {
258            return Err(Error::ExpectedExternalPtr(value.clone()));
259        }
260
261        // check type by downcasting
262        let boxed_ptr = unsafe {
263            value
264                .external_ptr_addr::<Box<dyn Any>>()
265                .cast_const()
266                .as_ref()
267                .ok_or_else(|| Error::ExpectedExternalNonNullPtr(value.clone()))?
268        };
269
270        if boxed_ptr.downcast_ref::<T>().is_none() {
271            return Err(Error::ExpectedExternalPtrType(
272                value.clone(),
273                std::any::type_name::<T>().to_string(),
274            ));
275        }
276
277        unsafe { Ok(std::mem::transmute::<&Robj, &ExternalPtr<T>>(value)) }
278    }
279}
280
281impl<T: 'static> TryFrom<&mut Robj> for &mut ExternalPtr<T> {
282    type Error = Error;
283
284    fn try_from(value: &mut Robj) -> Result<Self> {
285        if !value.is_external_pointer() {
286            return Err(Error::ExpectedExternalPtr(value.clone()));
287        }
288
289        // check type by downcasting
290        let boxed_ptr = unsafe {
291            value
292                .external_ptr_addr::<Box<dyn Any>>()
293                .cast_const()
294                .as_ref()
295                .ok_or_else(|| Error::ExpectedExternalNonNullPtr(value.clone()))?
296        };
297
298        if boxed_ptr.downcast_ref::<T>().is_none() {
299            return Err(Error::ExpectedExternalPtrType(
300                value.clone(),
301                std::any::type_name::<T>().to_string(),
302            ));
303        }
304
305        unsafe { Ok(std::mem::transmute::<&mut Robj, &mut ExternalPtr<T>>(value)) }
306    }
307}
308
309impl<T: 'static> TryFrom<&Robj> for ExternalPtr<T> {
310    type Error = Error;
311
312    fn try_from(robj: &Robj) -> Result<Self> {
313        let result: &Self = robj.try_into()?;
314        Ok(result.clone())
315    }
316}
317
318impl<T: 'static> TryFrom<Robj> for ExternalPtr<T> {
319    type Error = Error;
320
321    fn try_from(robj: Robj) -> Result<Self> {
322        <ExternalPtr<T>>::try_from(&robj)
323    }
324}
325
326impl<T> From<ExternalPtr<T>> for Robj {
327    fn from(val: ExternalPtr<T>) -> Self {
328        val.robj
329    }
330}
331
332impl<T> From<Option<ExternalPtr<T>>> for Robj {
333    fn from(value: Option<ExternalPtr<T>>) -> Self {
334        match value {
335            None => nil_value(),
336            Some(value) => value.into(),
337        }
338    }
339}
340
341impl<T: Debug + 'static> std::fmt::Debug for ExternalPtr<T> {
342    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343        (&**self as &T).fmt(f)
344    }
345}
346
347impl<T: 'static> AsRef<T> for ExternalPtr<T> {
348    fn as_ref(&self) -> &T {
349        self.addr()
350    }
351}
352
353impl<T: 'static> AsMut<T> for ExternalPtr<T> {
354    fn as_mut(&mut self) -> &mut T {
355        self.addr_mut()
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use std::error::Error;
362
363    use super::*;
364    use extendr_engine::with_r;
365
366    #[derive(Debug)]
367    struct BareWrapper(i32);
368
369    #[test]
370    fn externalptr_is_ptr() -> std::result::Result<(), Box<dyn Error>> {
371        with_r(|| {
372            let a = BareWrapper(42);
373            let b = BareWrapper(42);
374            assert_eq!(a.0, b.0);
375
376            let a_ptr = std::ptr::addr_of!(a);
377            let b_ptr = std::ptr::addr_of!(b);
378            let a_externalptr = ExternalPtr::new(a);
379            let b_externalptr = ExternalPtr::new(b);
380
381            assert_ne!(
382                a_ptr, b_ptr,
383                "pointers has to be equal by address, not value"
384            );
385
386            assert_ne!(
387                a_externalptr.robj, b_externalptr.robj,
388                "R only knows about the pointer, and not the pointee"
389            );
390            assert_ne!(
391                a_externalptr, b_externalptr,
392                "ExternalPtr acts exactly like a pointer"
393            );
394            assert_ne!(&a_externalptr, &b_externalptr,);
395            Ok(())
396        })
397    }
398
399    #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
400    struct Wrapper(i32);
401
402    #[test]
403    fn compare_externalptr_pointee() -> std::result::Result<(), Box<dyn Error>> {
404        with_r(|| {
405            let a = Wrapper(42);
406            let b = Wrapper(42);
407            let a_externalptr = ExternalPtr::new(a);
408            let b_externalptr = ExternalPtr::new(b);
409            assert_eq!(a_externalptr.as_ref(), b_externalptr.as_ref());
410
411            // let's test more use of `PartialOrd` on `T`
412            let a_externalptr = ExternalPtr::new(Wrapper(50));
413            let b_externalptr = ExternalPtr::new(Wrapper(60));
414            assert!(a_externalptr.as_ref() <= b_externalptr.as_ref());
415            assert_eq!(
416                a_externalptr.as_ref().max(b_externalptr.as_ref()),
417                &Wrapper(60)
418            );
419            Ok(())
420        })
421    }
422}