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}