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.0", 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.0",
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 Deref for Rstr {
129    type Target = str;
130
131    /// Treat `Rstr` like `&str`.
132    fn deref(&self) -> &Self::Target {
133        self.into()
134    }
135}
136
137/// Defer comparison to R's string interner
138impl PartialEq<Rstr> for Rstr {
139    fn eq(&self, other: &Rstr) -> bool {
140        unsafe { self.robj.get() == other.robj.get() }
141    }
142}
143
144/// Let performant than comparing [Rstr] directly as
145/// we need to convert [Rstr] to a string slice first
146impl PartialEq<str> for Rstr {
147    /// Compare a `Rstr` with a string slice.
148    fn eq(&self, other: &str) -> bool {
149        self.as_ref() == other
150    }
151}
152
153impl PartialEq<Rstr> for &str {
154    /// Compare a `Rstr` with a string slice.
155    fn eq(&self, other: &Rstr) -> bool {
156        *self == other.as_ref()
157    }
158}
159
160impl PartialEq<&str> for Rstr {
161    /// Compare a `Rstr` with a string slice.
162    fn eq(&self, other: &&str) -> bool {
163        self.as_ref() == *other
164    }
165}
166
167impl PartialEq<Rstr> for &&str {
168    /// Compare a `Rstr` with a string slice.
169    fn eq(&self, other: &Rstr) -> bool {
170        **self == other.as_ref()
171    }
172}
173
174impl std::fmt::Debug for Rstr {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        if self.is_na() {
177            write!(f, "NA_CHARACTER")
178        } else {
179            let s: &str = self.as_ref();
180            write!(f, "{:?}", s)
181        }
182    }
183}
184
185impl std::fmt::Display for Rstr {
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        let s: &str = self.as_ref();
188        write!(f, "{}", s)
189    }
190}
191
192impl CanBeNA for Rstr {
193    fn is_na(&self) -> bool {
194        unsafe { self.robj.get() == R_NaString }
195    }
196
197    fn na() -> Self {
198        unsafe {
199            Self {
200                robj: Robj::from_sexp(R_NaString),
201            }
202        }
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate as extendr_api;
210
211    #[test]
212    fn test_rstr_as_char() {
213        test! {
214            let chr = r!(Rstr::from("xyz"));
215            let x = chr.as_char().unwrap();
216            assert_eq!(x.as_ref(), "xyz");
217        }
218    }
219}