extendr_api/wrapper/
s4.rs

1//! S4 class support.
2//!
3//! It is not possible to create an S4 class from R's C-API, and thus it is
4//! not possible to do so in Rust. But an S4 class can be instantiated.
5//!
6//! Thus, the S4 class definition must be evaluated prior to using [`S4::new`].
7//! Conveniently, to inline the defintion of an S4 class with R, one can
8//! use [`S4::set_class`].
9//!
10//! Ideally, in an R-package setting, there will be no calls to `set_class`,
11//! and the definition of an S4-class will be present in the `/R` folder.
12//!
13//! ```r
14//! person_class <- setClass(
15//!   "person",
16//!   slots = c(name = "character", age = "integer")
17//! )
18//!
19//! person_class(name = "Lubo", age = 74L)
20//! #> An object of class "person"
21//! #> Slot "name":
22//! #> [1] "Lubo"
23//! #>
24//! #> Slot "age":
25//! #> [1] 74
26//! ```
27//! Now, `person` can be instantiated from Rust.
28//!
29
30use super::*;
31use extendr_ffi::{R_do_slot, R_do_slot_assign, R_has_slot};
32
33#[derive(PartialEq, Clone)]
34pub struct S4 {
35    pub(crate) robj: Robj,
36}
37
38impl S4 {
39    /// Create a S4 class.
40    ///
41    /// Equivalent to R's `setClass`.
42    ///
43    /// Example:
44    ///
45    /// ```
46    /// use extendr_api::prelude::*;
47    ///
48    /// test! {
49    ///     let class = S4::set_class("fred", pairlist!(x="numeric"), r!(()))?;
50    /// }
51    /// ```
52    pub fn set_class(name: &str, representation: Pairlist, contains: Robj) -> Result<S4> {
53        use crate as extendr_api;
54        let res = R!(r#"setClass({{name}}, {{representation}}, {{contains}})"#)?;
55        res.try_into()
56    }
57
58    /// Create a S4 object.
59    ///
60    /// Example:
61    /// ```
62    /// use extendr_api::prelude::*;
63    ///
64    /// test! {
65    ///     S4::set_class("fred", pairlist!(x="numeric"), r!(()))?;
66    ///     let mut robj : S4 = R!(r#"new("fred")"#)?.try_into()?;
67    /// }
68    /// ```
69    pub fn new(name: &str) -> Result<S4> {
70        use crate as extendr_api;
71        let res = R!(r#"new({{name}})"#)?;
72        res.try_into()
73    }
74
75    /// Get a named slot from a S4 object.
76    ///
77    /// Example:
78    /// ```
79    /// use extendr_api::prelude::*;
80    ///
81    /// test! {
82    ///     S4::set_class("fred", pairlist!(xyz="numeric"), r!(()))?;
83    ///     let robj : S4 = R!(r#"new("fred")"#)?.try_into()?;
84    ///     assert_eq!(robj.get_slot("xyz").unwrap().len(), 0);
85    /// }
86    /// ```
87    pub fn get_slot<'a, N>(&self, name: N) -> Option<Robj>
88    where
89        Self: 'a,
90        Robj: From<N> + 'a,
91    {
92        let name = Robj::from(name);
93        unsafe {
94            if R_has_slot(self.get(), name.get()) != 0 {
95                Some(Robj::from_sexp(R_do_slot(self.get(), name.get())))
96            } else {
97                None
98            }
99        }
100    }
101
102    /// Set a named slot in a S4 object.
103    ///
104    /// Example:
105    /// ```
106    /// use extendr_api::prelude::*;
107    ///
108    /// test! {
109    ///     S4::set_class("fred", pairlist!(xyz="numeric"), r!(()))?;
110    ///     let mut robj : S4 = R!(r#"new("fred")"#)?.try_into()?;
111    ///     let xyz = sym!(xyz);
112    ///     assert_eq!(robj.get_slot(xyz.clone()).unwrap().len(), 0);
113    ///     robj.set_slot(xyz.clone(), r!([0.0, 1.0]));
114    ///     assert_eq!(robj.get_slot(xyz), Some(r!([0.0, 1.0])));
115    /// }
116    /// ```
117    pub fn set_slot<N, V>(&mut self, name: N, value: V) -> Result<S4>
118    where
119        N: Into<Robj>,
120        V: Into<Robj>,
121    {
122        let name = name.into();
123        let value = value.into();
124        single_threaded(|| unsafe {
125            catch_r_error(|| R_do_slot_assign(self.get(), name.get(), value.get()))
126                .map(|_| self.clone())
127        })
128    }
129
130    /// Check if a named slot exists.
131    ///
132    /// Example:
133    /// ```
134    /// use extendr_api::prelude::*;
135    ///
136    /// test! {
137    ///     S4::set_class("fred", pairlist!(xyz="numeric"), r!(()))?;
138    ///     let robj : S4 = R!(r#"new("fred")"#)?.try_into()?;
139    ///     assert_eq!(robj.has_slot("xyz"), true);
140    /// }
141    /// ```
142    pub fn has_slot<'a, N>(&self, name: N) -> bool
143    where
144        Self: 'a,
145        Robj: From<N> + 'a,
146    {
147        let name = Robj::from(name);
148        unsafe { R_has_slot(self.get(), name.get()) != 0 }
149    }
150}
151
152// TODO: Think about these functions in the future.
153//
154// Currently, S4 support is not a top priority, but we hope that what we have
155// covered the basics for now.
156//
157// extern "C" {
158//     pub fn R_S4_extends(klass: SEXP, useTable: SEXP) -> SEXP;
159// }
160// extern "C" {
161//     pub fn R_getClassDef(what: *const ::std::os::raw::c_char) -> SEXP;
162// }
163// extern "C" {
164//     pub fn R_getClassDef_R(what: SEXP) -> SEXP;
165// }
166// extern "C" {
167//     pub fn R_has_methods_attached() -> Rboolean;
168// }
169// extern "C" {
170//     pub fn R_isVirtualClass(class_def: SEXP, env: SEXP) -> Rboolean;
171// }
172// extern "C" {
173//     pub fn R_extends(class1: SEXP, class2: SEXP, env: SEXP) -> Rboolean;
174// }
175// extern "C" {
176//     pub fn R_check_class_and_super(
177//         x: SEXP,
178//         valid: *mut *const ::std::os::raw::c_char,
179//         rho: SEXP,
180//     ) -> ::std::os::raw::c_int;
181// }
182// extern "C" {
183//     pub fn R_check_class_etc(
184//         x: SEXP,
185//         valid: *mut *const ::std::os::raw::c_char,
186//     ) -> ::std::os::raw::c_int;
187// }
188
189impl std::fmt::Debug for S4 {
190    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191        f.debug_struct("S4").finish()
192    }
193}
194
195impl From<Option<S4>> for Robj {
196    fn from(value: Option<S4>) -> Self {
197        match value {
198            None => nil_value(),
199            Some(value) => value.into(),
200        }
201    }
202}