nu_utils/strings/
shared.rs

1use std::{
2    borrow::Borrow,
3    fmt::{Arguments, Debug, Display},
4    hash::Hash,
5    ops::Deref,
6};
7
8use serde::{Deserialize, Serialize};
9
10/// An owned, immutable string with compact storage and efficient cloning.
11///
12/// `SharedString` is designed for immutable strings that are frequently cloned and hold ownership.
13/// It offers similar characteristics to [`Arc<str>`](std::sync::Arc) but with several key
14/// optimizations for improved efficiency and memory usage:
15///
16/// - **Efficient Cloning:**
17///   Cloning a `SharedString` is very inexpensive.
18///   It typically involves just a pointer copy and an atomic reference count increment, without
19///   copying the actual string data.
20///
21/// - **Small String Optimization (SSO):**
22///   For shorter strings, the data is stored directly within the `SharedString` struct, keeping
23///   the data on the stack and avoiding indirection.
24///
25/// - **Static String Re-use:**
26///   Strings with a `'static` lifetime are directly referenced, avoiding unnecessary copies or
27///   allocations.
28///
29/// - **Niche Optimization:**
30///   `SharedString` allows niche-optimization, meaning that [`Option<SharedString>`] has the same
31///   memory footprint as `SharedString`.
32///
33/// - **Compact Size:**
34///   On 64-bit systems, `SharedString` is 16 bytes.
35///   This is achieved by disregarding the capacity of a `String` since we only hold the string as
36///   immutable.
37///   
38/// Internally, `SharedString` is powered by [`lean_string::LeanString`], which provides the
39/// underlying implementation for these optimizations.
40pub struct SharedString(lean_string::LeanString);
41
42const _: () = const {
43    assert!(size_of::<SharedString>() == size_of::<[usize; 2]>());
44    assert!(size_of::<SharedString>() == size_of::<Option<SharedString>>());
45};
46
47impl SharedString {
48    /// Returns a string slice containing the entire `SharedString`.
49    #[inline]
50    pub fn as_str(&self) -> &str {
51        self.0.as_str()
52    }
53
54    /// Returns a byte slice of this `SharedString`'s contents.
55    #[inline]
56    pub fn as_bytes(&self) -> &[u8] {
57        self.0.as_bytes()
58    }
59
60    /// Returns the length of this `SharedString`, in bytes.
61    #[inline]
62    pub fn len(&self) -> usize {
63        self.0.len()
64    }
65
66    /// Returns `true` if the `SharedString` has a length of 0, `false` otherwise.
67    #[inline]
68    pub fn is_empty(&self) -> bool {
69        self.0.is_empty()
70    }
71
72    /// Returns a `SharedString` by taking ownership of an allocation.
73    #[inline]
74    pub fn from_string(string: String) -> Self {
75        Self(lean_string::LeanString::from(string))
76    }
77
78    /// Returns a `SharedString` pointing to the given slice, without copying.
79    ///
80    /// By using this function instead of [`from_string`](Self::from_string), we can avoid any
81    /// copying and always refer to the provided static string slice.
82    #[inline]
83    pub fn from_static_str(string: &'static str) -> Self {
84        Self(lean_string::LeanString::from_static_str(string))
85    }
86
87    /// Builds a new `SharedString` from the given formatting arguments.
88    ///
89    /// You can get an [`Arguments`] instance by calling [`format_args!`].
90    /// This function is used when using [`sformat!`](crate::sformat) instead of [`format!`] to
91    /// create a `SharedString`.
92    #[inline]
93    pub fn from_fmt(arguments: Arguments) -> Self {
94        match arguments.as_str() {
95            Some(static_str) => Self::from_static_str(static_str),
96            None => Self::from_string(std::fmt::format(arguments)),
97        }
98    }
99}
100
101impl AsRef<str> for SharedString {
102    #[inline]
103    fn as_ref(&self) -> &str {
104        self.as_str()
105    }
106}
107
108impl Borrow<str> for SharedString {
109    #[inline]
110    fn borrow(&self) -> &str {
111        self.as_str()
112    }
113}
114
115impl Clone for SharedString {
116    #[inline]
117    fn clone(&self) -> Self {
118        Self(self.0.clone())
119    }
120}
121
122impl Debug for SharedString {
123    #[inline]
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        Debug::fmt(&self.0, f)
126    }
127}
128
129impl Default for SharedString {
130    #[inline]
131    fn default() -> Self {
132        Self(Default::default())
133    }
134}
135
136impl Deref for SharedString {
137    type Target = str;
138
139    #[inline]
140    fn deref(&self) -> &Self::Target {
141        self.as_str()
142    }
143}
144
145impl Display for SharedString {
146    #[inline]
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        Display::fmt(&self.0, f)
149    }
150}
151
152impl Eq for SharedString {}
153
154impl From<String> for SharedString {
155    #[inline]
156    fn from(string: String) -> Self {
157        Self::from_string(string)
158    }
159}
160
161impl From<&'static str> for SharedString {
162    #[inline]
163    fn from(string: &'static str) -> Self {
164        Self::from_static_str(string)
165    }
166}
167
168impl Hash for SharedString {
169    #[inline]
170    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
171        self.0.hash(state);
172    }
173}
174
175impl Ord for SharedString {
176    #[inline]
177    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
178        self.0.cmp(&other.0)
179    }
180}
181
182impl PartialEq for SharedString {
183    #[inline]
184    fn eq(&self, other: &Self) -> bool {
185        self.0 == other.0
186    }
187}
188
189impl PartialOrd for SharedString {
190    #[inline]
191    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
192        Some(self.cmp(other))
193    }
194}
195
196impl Serialize for SharedString {
197    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
198    where
199        S: serde::Serializer,
200    {
201        self.0.serialize(serializer)
202    }
203}
204
205impl<'de> Deserialize<'de> for SharedString {
206    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
207    where
208        D: serde::Deserializer<'de>,
209    {
210        Ok(Self(lean_string::LeanString::deserialize(deserializer)?))
211    }
212}
213
214/// A macro for creating [`SharedString`] instances from format arguments.
215///
216/// This macro works similarly to [`format!`] but returns a [`SharedString`] instead of a [`String`].
217/// It attempts to optimize for `'static` string literals.
218#[macro_export]
219macro_rules! sformat {
220    ($fmt:expr) => {
221        $crate::strings::SharedString::from_fmt(::std::format_args!($fmt))
222    };
223
224    ($fmt:expr, $($args:tt)*) => {
225        $crate::strings::SharedString::from_fmt(::std::format_args!($fmt, $($args)*))
226    };
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232
233    #[test]
234    fn macro_works() {
235        assert!(sformat!("").is_empty());
236        assert_eq!(
237            sformat!("Hello World"),
238            SharedString::from_static_str("Hello World")
239        );
240        assert_eq!(
241            sformat!("Hello {}", "World"),
242            SharedString::from_static_str("Hello World")
243        );
244    }
245}