Skip to main content

extendr_api/wrapper/
rstr.rs

1use super::*;
2use extendr_ffi::{R_BlankString, R_NaString, R_NilValue, Rf_xlength, R_CHAR, SEXPTYPE, TYPEOF};
3/// Wrapper for creating CHARSXP objects.
4/// These are used only as the contents of a character
5/// vector.
6///
7/// ```
8/// use extendr_api::prelude::*;
9/// test! {
10///     let chr = r!(Rstr::from("xyz"));
11///     assert_eq!(chr.as_char().unwrap().as_ref(), "xyz");
12/// }
13/// ```
14///
15#[derive(Clone)]
16pub struct Rstr {
17    pub(crate) robj: Robj,
18}
19
20/// Returns a rust string-slice based on the provided `SEXP`, which is of type
21/// [`SEXPTYPE::CHARSXP`]. Note that the length of a `CHARSXP` is exactly
22/// the number of non-null bytes in said R character.
23pub(crate) unsafe fn charsxp_to_str(charsxp: SEXP) -> Option<&'static str> {
24    assert_eq!(TYPEOF(charsxp), SEXPTYPE::CHARSXP);
25    if charsxp == R_NilValue {
26        None
27    } else if charsxp == R_NaString {
28        Some(<&str>::na())
29    } else if charsxp == R_BlankString {
30        Some("")
31    } else {
32        let length = Rf_xlength(charsxp);
33        let all_bytes =
34            std::slice::from_raw_parts(R_CHAR(charsxp).cast(), length.try_into().unwrap());
35        Some(std::str::from_utf8_unchecked(all_bytes))
36    }
37}
38
39impl Rstr {
40    /// Make a character object from a string.
41    ///
42    /// # Deprecated
43    /// Use `Rstr::from()` or `.into()` instead, which implement the standard `From<&str>` trait.
44    ///
45    /// # Examples
46    /// ```
47    /// use extendr_api::prelude::*;
48    /// # fn example() {
49    /// let rstr = Rstr::from("hello");
50    /// // or
51    /// let rstr: Rstr = "hello".into();
52    /// # }
53    /// ```
54    #[deprecated(since = "0.8.1", note = "Use `Rstr::from()` or `.into()` instead")]
55    pub fn from_string(val: &str) -> Self {
56        Rstr {
57            robj: unsafe { Robj::from_sexp(str_to_character(val)) },
58        }
59    }
60
61    /// Get the string from a character object.
62    /// If the string is NA, then the special na_str() is returned.
63    ///
64    /// # Deprecated
65    /// Use `.as_ref()` (from `AsRef<str>` trait) or rely on `Deref` coercion instead.
66    ///
67    /// # Examples
68    /// ```
69    /// use extendr_api::prelude::*;
70    /// # fn example() {
71    /// # let rstr = Rstr::from("hello");
72    /// let s: &str = rstr.as_ref();
73    /// // or use Deref coercion
74    /// let len = rstr.len(); // calls str::len() via Deref
75    /// # }
76    /// ```
77    #[deprecated(
78        since = "0.8.1",
79        note = "Use `.as_ref()` or rely on `Deref` coercion instead"
80    )]
81    pub fn as_str(&self) -> &str {
82        self.into()
83    }
84}
85
86impl AsRef<str> for Rstr {
87    /// Treat a Rstr as a string slice.
88    fn as_ref(&self) -> &str {
89        self.into()
90    }
91}
92
93impl From<String> for Rstr {
94    /// Convert a String to a Rstr.
95    fn from(s: String) -> Self {
96        Self::from(s.as_str())
97    }
98}
99
100impl From<&str> for Rstr {
101    /// Convert a string slice to a Rstr.
102    fn from(s: &str) -> Self {
103        Rstr {
104            robj: unsafe { Robj::from_sexp(str_to_character(s)) },
105        }
106    }
107}
108
109impl From<&Rstr> for &str {
110    fn from(value: &Rstr) -> Self {
111        unsafe {
112            let charsxp = value.robj.get();
113            rstr::charsxp_to_str(charsxp).unwrap()
114        }
115    }
116}
117
118impl From<Option<String>> for Rstr {
119    fn from(value: Option<String>) -> Self {
120        if let Some(string) = value {
121            Self::from(string)
122        } else {
123            Self { robj: na_string() }
124        }
125    }
126}
127
128impl From<Option<&str>> for Rstr {
129    fn from(value: Option<&str>) -> Self {
130        if let Some(string_ref) = value {
131            Self::from(string_ref)
132        } else {
133            Self { robj: na_string() }
134        }
135    }
136}
137
138impl Deref for Rstr {
139    type Target = str;
140
141    /// Treat `Rstr` like `&str`.
142    fn deref(&self) -> &Self::Target {
143        self.into()
144    }
145}
146
147/// Defer comparison to R's string interner
148impl PartialEq<Rstr> for Rstr {
149    fn eq(&self, other: &Rstr) -> bool {
150        unsafe { self.robj.get() == other.robj.get() }
151    }
152}
153
154/// Let performant than comparing [Rstr] directly as
155/// we need to convert [Rstr] to a string slice first
156impl PartialEq<str> for Rstr {
157    /// Compare a `Rstr` with a string slice.
158    fn eq(&self, other: &str) -> bool {
159        self.as_ref() == other
160    }
161}
162
163impl PartialEq<Rstr> for &str {
164    /// Compare a `Rstr` with a string slice.
165    fn eq(&self, other: &Rstr) -> bool {
166        *self == other.as_ref()
167    }
168}
169
170impl PartialEq<&str> for Rstr {
171    /// Compare a `Rstr` with a string slice.
172    fn eq(&self, other: &&str) -> bool {
173        self.as_ref() == *other
174    }
175}
176
177impl PartialEq<Rstr> for &&str {
178    /// Compare a `Rstr` with a string slice.
179    fn eq(&self, other: &Rstr) -> bool {
180        **self == other.as_ref()
181    }
182}
183
184impl std::fmt::Debug for Rstr {
185    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186        if self.is_na() {
187            write!(f, "NA_CHARACTER")
188        } else {
189            let s: &str = self.as_ref();
190            write!(f, "{:?}", s)
191        }
192    }
193}
194
195impl std::fmt::Display for Rstr {
196    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
197        let s: &str = self.as_ref();
198        write!(f, "{}", s)
199    }
200}
201
202impl CanBeNA for Rstr {
203    fn is_na(&self) -> bool {
204        unsafe { self.robj.get() == R_NaString }
205    }
206
207    fn na() -> Self {
208        unsafe {
209            Self {
210                robj: Robj::from_sexp(R_NaString),
211            }
212        }
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use super::*;
219    use crate as extendr_api;
220
221    #[test]
222    fn test_rstr_as_char() {
223        test! {
224            let chr = r!(Rstr::from("xyz"));
225            let x = chr.as_char().unwrap();
226            assert_eq!(x.as_ref(), "xyz");
227        }
228    }
229
230    #[test]
231    fn test_rstr_from_str_ref() {
232        test! {
233            assert_eq!(Rstr::from(Some("value")), Rstr::from("value"));
234        }
235    }
236}