Skip to main content

nu_utils/strings/
unique.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.
10///
11/// `UniqueString` is designed for immutable strings that are not frequently cloned and hold ownership.
12/// It offers similar characteristics to `Box<str>` but with several key
13/// optimizations for improved efficiency and memory usage:
14///
15/// - **Efficient for Unique Strings:**
16///   When strings are not frequently cloned, `UniqueString` can be more performant than
17///   reference-counted alternatives like [`SharedString`](super::SharedString) as it avoids the
18///   overhead of atomic reference counting.
19///
20/// - **Small String Optimization (SSO):**
21///   For shorter strings, the data is stored directly within the `UniqueString` 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///   `UniqueString` allows niche-optimization, meaning that [`Option<UniqueString>`] has the same
30///   memory footprint as `UniqueString`.
31///
32/// - **Compact Size:**
33///   On 64-bit systems, `UniqueString` 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, `UniqueString` is powered by [`byteyarn::Yarn`], which provides the
38/// underlying implementation for these optimizations.
39#[derive(derive_more::Debug, Clone, Default)]
40#[debug("{_0:?}")]
41pub struct UniqueString(byteyarn::Yarn);
42
43const _: () = const {
44    assert!(size_of::<UniqueString>() == size_of::<[usize; 2]>());
45    assert!(size_of::<UniqueString>() == size_of::<Option<UniqueString>>());
46};
47
48impl UniqueString {
49    /// Returns a string slice containing the entire `UniqueString`.
50    #[inline]
51    pub fn as_str(&self) -> &str {
52        self.0.as_str()
53    }
54
55    /// Returns a byte slice of this `UniqueString`'s contents.
56    #[inline]
57    pub fn as_bytes(&self) -> &[u8] {
58        self.0.as_bytes()
59    }
60
61    /// Returns the length of this `UniqueString`, in bytes.
62    #[inline]
63    pub fn len(&self) -> usize {
64        self.0.len()
65    }
66
67    /// Returns `true` if the `UniqueString` 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 `UniqueString` by taking ownership of an allocation.
74    #[inline]
75    pub fn from_string(string: String) -> Self {
76        Self(byteyarn::Yarn::from_string(string))
77    }
78
79    /// Returns a `UniqueString` 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(byteyarn::Yarn::from_static(string))
86    }
87
88    /// Builds a new `UniqueString` from the given formatting arguments.
89    ///
90    /// You can get an [`Arguments`] instance by calling [`format_args!`].
91    /// This function is used when using [`uformat!`](crate::uformat) instead of [`format!`] to
92    /// create a `UniqueString`.
93    #[inline]
94    pub fn from_fmt(arguments: Arguments) -> Self {
95        Self(byteyarn::Yarn::from_fmt(arguments))
96    }
97}
98
99impl AsRef<str> for UniqueString {
100    #[inline]
101    fn as_ref(&self) -> &str {
102        self.as_str()
103    }
104}
105
106impl Borrow<str> for UniqueString {
107    #[inline]
108    fn borrow(&self) -> &str {
109        self.as_str()
110    }
111}
112
113impl Deref for UniqueString {
114    type Target = str;
115
116    #[inline]
117    fn deref(&self) -> &Self::Target {
118        self.as_str()
119    }
120}
121
122impl Display for UniqueString {
123    #[inline]
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        Display::fmt(&self.0, f)
126    }
127}
128
129impl Eq for UniqueString {}
130
131impl From<String> for UniqueString {
132    #[inline]
133    fn from(string: String) -> Self {
134        Self::from_string(string)
135    }
136}
137
138impl From<&'static str> for UniqueString {
139    #[inline]
140    fn from(string: &'static str) -> Self {
141        Self::from_static_str(string)
142    }
143}
144
145impl Hash for UniqueString {
146    #[inline]
147    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
148        self.0.hash(state);
149    }
150}
151
152impl Ord for UniqueString {
153    #[inline]
154    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
155        self.0.cmp(&other.0)
156    }
157}
158
159impl PartialEq for UniqueString {
160    #[inline]
161    fn eq(&self, other: &Self) -> bool {
162        self.0 == other.0
163    }
164}
165
166impl PartialOrd for UniqueString {
167    #[inline]
168    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
169        Some(self.cmp(other))
170    }
171}
172
173impl Serialize for UniqueString {
174    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
175    where
176        S: serde::Serializer,
177    {
178        self.0.serialize(serializer)
179    }
180}
181
182impl<'de> Deserialize<'de> for UniqueString {
183    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
184    where
185        D: serde::Deserializer<'de>,
186    {
187        let s = String::deserialize(deserializer)?;
188        Ok(Self::from_string(s))
189    }
190}
191
192/// A macro for creating [`UniqueString`] instances from format arguments.
193///
194/// This macro works similarly to [`format!`] but returns a [`UniqueString`] instead of a [`String`].
195/// It attempts to optimize for `'static` string literals.
196#[macro_export]
197macro_rules! uformat {
198    ($fmt:expr) => {
199        $crate::strings::UniqueString::from_fmt(::std::format_args!($fmt))
200    };
201
202    ($fmt:expr, $($args:tt)*) => {
203        $crate::strings::UniqueString::from_fmt(::std::format_args!($fmt, $($args)*))
204    };
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210
211    #[test]
212    fn macro_works() {
213        assert!(uformat!("").is_empty());
214        assert_eq!(
215            uformat!("Hello World"),
216            UniqueString::from_static_str("Hello World")
217        );
218        assert_eq!(
219            uformat!("Hello {}", "World"),
220            UniqueString::from_static_str("Hello World")
221        );
222    }
223}