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