extendr_api/wrapper/
rstr.rs1use super::*;
2use extendr_ffi::{R_BlankString, R_NaString, R_NilValue, Rf_xlength, R_CHAR, SEXPTYPE, TYPEOF};
3#[derive(Clone)]
16pub struct Rstr {
17 pub(crate) robj: Robj,
18}
19
20pub(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 #[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 #[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 fn as_ref(&self) -> &str {
89 self.into()
90 }
91}
92
93impl From<String> for Rstr {
94 fn from(s: String) -> Self {
96 Self::from(s.as_str())
97 }
98}
99
100impl From<&str> for Rstr {
101 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 fn deref(&self) -> &Self::Target {
143 self.into()
144 }
145}
146
147impl PartialEq<Rstr> for Rstr {
149 fn eq(&self, other: &Rstr) -> bool {
150 unsafe { self.robj.get() == other.robj.get() }
151 }
152}
153
154impl PartialEq<str> for Rstr {
157 fn eq(&self, other: &str) -> bool {
159 self.as_ref() == other
160 }
161}
162
163impl PartialEq<Rstr> for &str {
164 fn eq(&self, other: &Rstr) -> bool {
166 *self == other.as_ref()
167 }
168}
169
170impl PartialEq<&str> for Rstr {
171 fn eq(&self, other: &&str) -> bool {
173 self.as_ref() == *other
174 }
175}
176
177impl PartialEq<Rstr> for &&str {
178 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}