Skip to main content

rajac_base/
shared_string.rs

1//! Shared string wrapper type for efficient string handling.
2
3use crate::result::RajacResult;
4use ecow::EcoString;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7/// A wrapper around ecow::EcoString for efficient shared string storage.
8///
9/// This type provides copy-on-write semantics with cheap cloning,
10/// making it ideal for storing strings that are shared across multiple
11/// parts of the compiler without unnecessary allocations.
12#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct SharedString(pub EcoString);
14
15impl SharedString {
16    /// Creates a new SharedString from the given string.
17    pub fn new(s: impl Into<String>) -> Self {
18        Self(EcoString::from(s.into()))
19    }
20
21    /// Creates a new empty SharedString.
22    pub fn empty() -> Self {
23        Self(EcoString::from(""))
24    }
25
26    /// Clears the contents of the string.
27    pub fn clear(&mut self) {
28        self.0 = EcoString::from("");
29    }
30
31    /// Appends a string slice to this string.
32    pub fn push_str(&mut self, string: &str) {
33        let mut new_string = self.0.to_string();
34        new_string.push_str(string);
35        self.0 = EcoString::from(new_string);
36    }
37
38    /// Returns the underlying string as a string slice.
39    pub fn as_str(&self) -> &str {
40        &self.0
41    }
42
43    /// Returns the length of the string.
44    pub fn len(&self) -> usize {
45        self.0.len()
46    }
47
48    /// Returns true if the string is empty.
49    pub fn is_empty(&self) -> bool {
50        self.0.is_empty()
51    }
52
53    pub fn from_utf8(ut8_bytes: &[u8]) -> RajacResult<Self> {
54        Ok(Self(EcoString::from(str::from_utf8(ut8_bytes)?)))
55    }
56}
57
58/// Creates a new SharedString from a format string.
59///
60/// This macro delegates to `eco_format` from the ecow crate.
61#[macro_export]
62macro_rules! shared_format {
63    ($($arg:tt)*) => {
64        $crate::shared_string::SharedString(ecow::eco_format!($($arg)*))
65    };
66}
67
68impl Default for SharedString {
69    fn default() -> Self {
70        Self::empty()
71    }
72}
73
74impl From<String> for SharedString {
75    fn from(s: String) -> Self {
76        Self(EcoString::from(s))
77    }
78}
79
80impl From<&str> for SharedString {
81    fn from(s: &str) -> Self {
82        Self(EcoString::from(s))
83    }
84}
85
86impl From<Box<str>> for SharedString {
87    fn from(s: Box<str>) -> Self {
88        Self(EcoString::from(&*s))
89    }
90}
91
92impl From<&SharedString> for SharedString {
93    fn from(s: &SharedString) -> Self {
94        s.clone()
95    }
96}
97
98impl AsRef<str> for SharedString {
99    fn as_ref(&self) -> &str {
100        &self.0
101    }
102}
103
104impl std::borrow::Borrow<str> for SharedString {
105    fn borrow(&self) -> &str {
106        &self.0
107    }
108}
109
110impl std::fmt::Display for SharedString {
111    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112        write!(f, "{}", self.0)
113    }
114}
115
116impl std::fmt::Debug for SharedString {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        write!(f, "`{}`", self.0)
119    }
120}
121
122impl std::ops::Deref for SharedString {
123    type Target = str;
124
125    fn deref(&self) -> &Self::Target {
126        &self.0
127    }
128}
129
130impl PartialEq<str> for SharedString {
131    fn eq(&self, other: &str) -> bool {
132        self.as_str() == other
133    }
134}
135
136impl PartialEq<&str> for SharedString {
137    fn eq(&self, other: &&str) -> bool {
138        self.as_str() == *other
139    }
140}
141
142impl PartialEq<SharedString> for str {
143    fn eq(&self, other: &SharedString) -> bool {
144        self == other.as_str()
145    }
146}
147
148impl PartialEq<SharedString> for &str {
149    fn eq(&self, other: &SharedString) -> bool {
150        *self == other.as_str()
151    }
152}
153
154impl<'a, C> speedy::Readable<'a, C> for SharedString
155where
156    C: speedy::Context,
157{
158    fn read_from<R: speedy::Reader<'a, C>>(reader: &mut R) -> Result<Self, C::Error> {
159        let string = String::read_from(reader)?;
160        Ok(SharedString(EcoString::from(string)))
161    }
162}
163
164impl<C> speedy::Writable<C> for SharedString
165where
166    C: speedy::Context,
167{
168    fn write_to<W>(&self, writer: &mut W) -> Result<(), C::Error>
169    where
170        W: speedy::Writer<C> + ?Sized,
171    {
172        self.as_str().write_to(writer)
173    }
174}
175
176impl Serialize for SharedString {
177    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
178    where
179        S: Serializer,
180    {
181        serializer.serialize_str(self.as_str())
182    }
183}
184
185impl<'de> Deserialize<'de> for SharedString {
186    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
187    where
188        D: Deserializer<'de>,
189    {
190        let value = String::deserialize(deserializer)?;
191        Ok(value.into())
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198    use speedy::{Readable, Writable};
199
200    #[test]
201    fn test_shared_string_creation() {
202        let s1 = SharedString::new("hello");
203        let s2 = SharedString::from("world");
204        let s3: SharedString = "test".into();
205
206        assert_eq!(s1.as_str(), "hello");
207        assert_eq!(s2.as_str(), "world");
208        assert_eq!(s3.as_str(), "test");
209    }
210
211    #[test]
212    fn test_shared_string_equality() {
213        let s1 = SharedString::new("hello");
214        let s2 = SharedString::new("hello");
215        let s3 = SharedString::new("world");
216
217        assert_eq!(s1, s2);
218        assert_ne!(s1, s3);
219    }
220
221    #[test]
222    fn test_shared_string_clone() {
223        let s1 = SharedString::new("hello");
224        let s2 = s1.clone();
225
226        assert_eq!(s1, s2);
227        assert_eq!(s1.as_str(), s2.as_str());
228    }
229
230    #[test]
231    fn test_shared_string_default() {
232        let s = SharedString::default();
233        assert!(s.is_empty());
234        assert_eq!(s.len(), 0);
235    }
236
237    #[test]
238    fn test_shared_string_deref() {
239        let s = SharedString::new("hello");
240        assert_eq!(s.len(), 5);
241        assert_eq!(&s[0..2], "he");
242    }
243
244    #[test]
245    fn test_shared_string_display() {
246        let s = SharedString::new("hello");
247        assert_eq!(format!("{}", s), "hello");
248    }
249
250    #[test]
251    fn test_shared_string_speedy_serialization() {
252        let original = SharedString::new("hello world");
253
254        // Test writing
255        let buffer = original.write_to_vec().unwrap();
256        assert!(!buffer.is_empty());
257
258        // Test reading
259        let deserialized = SharedString::read_from_buffer(&buffer).unwrap();
260        assert_eq!(original, deserialized);
261        assert_eq!(deserialized.as_str(), "hello world");
262    }
263
264    #[test]
265    fn test_shared_string_speedy_empty_string() {
266        let original = SharedString::empty();
267
268        let buffer = original.write_to_vec().unwrap();
269        let deserialized = SharedString::read_from_buffer(&buffer).unwrap();
270
271        assert_eq!(original, deserialized);
272        assert!(deserialized.is_empty());
273    }
274
275    #[test]
276    fn test_shared_string_speedy_unicode() {
277        let original = SharedString::new("Hello 🌍 δΈ–η•Œ");
278
279        let buffer = original.write_to_vec().unwrap();
280        let deserialized = SharedString::read_from_buffer(&buffer).unwrap();
281
282        assert_eq!(original, deserialized);
283        assert_eq!(deserialized.as_str(), "Hello 🌍 δΈ–η•Œ");
284    }
285
286    #[test]
287    fn test_shared_string_format_macro() {
288        let name = "world";
289        let s = shared_format!("Hello, {}!", name);
290        assert_eq!(s.as_str(), "Hello, world!");
291    }
292
293    #[test]
294    fn test_shared_string_format_macro_multiple_args() {
295        let s = shared_format!("{} + {} = {}", 1, 2, 3);
296        assert_eq!(s.as_str(), "1 + 2 = 3");
297    }
298}