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.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 #[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 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 Deref for Rstr {
129 type Target = str;
130
131 fn deref(&self) -> &Self::Target {
133 self.into()
134 }
135}
136
137impl PartialEq<Rstr> for Rstr {
139 fn eq(&self, other: &Rstr) -> bool {
140 unsafe { self.robj.get() == other.robj.get() }
141 }
142}
143
144impl PartialEq<str> for Rstr {
147 fn eq(&self, other: &str) -> bool {
149 self.as_ref() == other
150 }
151}
152
153impl PartialEq<Rstr> for &str {
154 fn eq(&self, other: &Rstr) -> bool {
156 *self == other.as_ref()
157 }
158}
159
160impl PartialEq<&str> for Rstr {
161 fn eq(&self, other: &&str) -> bool {
163 self.as_ref() == *other
164 }
165}
166
167impl PartialEq<Rstr> for &&str {
168 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}