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_string("xyz"));
11///     assert_eq!(chr.as_char().unwrap().as_str(), "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    pub fn from_string(val: &str) -> Self {
42        Rstr {
43            robj: Robj::from_sexp(str_to_character(val)),
44        }
45    }
46
47    /// Get the string from a character object.
48    /// If the string is NA, then the special na_str() is returned.
49    pub fn as_str(&self) -> &str {
50        self.into()
51    }
52}
53
54impl AsRef<str> for Rstr {
55    /// Treat a Rstr as a string slice.
56    fn as_ref(&self) -> &str {
57        self.as_str()
58    }
59}
60
61impl From<String> for Rstr {
62    /// Convert a String to a Rstr.
63    fn from(s: String) -> Self {
64        Rstr::from_string(&s)
65    }
66}
67
68impl From<&str> for Rstr {
69    /// Convert a string slice to a Rstr.
70    fn from(s: &str) -> Self {
71        Rstr::from_string(s)
72    }
73}
74
75impl From<&Rstr> for &str {
76    fn from(value: &Rstr) -> Self {
77        unsafe {
78            let charsxp = value.robj.get();
79            rstr::charsxp_to_str(charsxp).unwrap()
80        }
81    }
82}
83
84impl From<Option<String>> for Rstr {
85    fn from(value: Option<String>) -> Self {
86        if let Some(string) = value {
87            Self::from(string)
88        } else {
89            Self { robj: na_string() }
90        }
91    }
92}
93
94impl Deref for Rstr {
95    type Target = str;
96
97    /// Treat `Rstr` like `&str`.
98    fn deref(&self) -> &Self::Target {
99        self.as_str()
100    }
101}
102
103/// Defer comparison to R's string interner
104impl PartialEq<Rstr> for Rstr {
105    fn eq(&self, other: &Rstr) -> bool {
106        unsafe { self.robj.get() == other.robj.get() }
107    }
108}
109
110/// Let performant than comparing [Rstr] directly as
111/// we need to convert [Rstr] to a string slice first
112impl PartialEq<str> for Rstr {
113    /// Compare a `Rstr` with a string slice.
114    fn eq(&self, other: &str) -> bool {
115        self.as_str() == other
116    }
117}
118
119impl PartialEq<Rstr> for &str {
120    /// Compare a `Rstr` with a string slice.
121    fn eq(&self, other: &Rstr) -> bool {
122        *self == other.as_str()
123    }
124}
125
126impl PartialEq<&str> for Rstr {
127    /// Compare a `Rstr` with a string slice.
128    fn eq(&self, other: &&str) -> bool {
129        self.as_str() == *other
130    }
131}
132
133impl PartialEq<Rstr> for &&str {
134    /// Compare a `Rstr` with a string slice.
135    fn eq(&self, other: &Rstr) -> bool {
136        **self == other.as_str()
137    }
138}
139
140impl std::fmt::Debug for Rstr {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        if self.is_na() {
143            write!(f, "NA_CHARACTER")
144        } else {
145            let s = self.as_str();
146            write!(f, "{:?}", s)
147        }
148    }
149}
150
151impl std::fmt::Display for Rstr {
152    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
153        let s = self.as_str();
154        write!(f, "{}", s)
155    }
156}
157
158impl CanBeNA for Rstr {
159    fn is_na(&self) -> bool {
160        unsafe { self.robj.get() == R_NaString }
161    }
162
163    fn na() -> Self {
164        unsafe {
165            Self {
166                robj: Robj::from_sexp(R_NaString),
167            }
168        }
169    }
170}