Skip to main content

nu_utils/strings/
shared.rs

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