logforth_core/
str.rs

1// Copyright 2024 FastLabs Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The [`Str`] type.
16//!
17//! This module implements a string type that combines `Cow<'static, str>` with `Cow<'a, str>`. A
18//! [`Str`] can hold borrowed, static, owned, or shared data. Internally, it's more efficient than a
19//! [`Cow`] to access because it doesn't need to hop through enum variants.
20//!
21//! Values can be converted into [`Str`]s either directly using methods like [`Str::new`], or
22//! generically through the [`ToStr`] trait.
23
24// This file is derived from https://github.com/emit-rs/emit/blob/097f5254/core/src/str.rs
25
26use std::borrow::Borrow;
27use std::borrow::Cow;
28use std::fmt;
29use std::hash;
30use std::marker::PhantomData;
31use std::sync::Arc;
32
33/// A string value.
34pub struct Str<'k> {
35    // This type is an optimized `Cow<str>`
36    // It avoids the cost of matching the variant to get the inner value
37    value: *const str,
38    owner: StrOwner,
39    marker: PhantomData<&'k str>,
40}
41
42// SAFETY: `Str` synchronizes through `Arc` when ownership is shared
43unsafe impl<'k> Send for Str<'k> {}
44// SAFETY: `Str` does not use interior mutability
45unsafe impl<'k> Sync for Str<'k> {}
46
47impl<'k> Default for Str<'k> {
48    fn default() -> Self {
49        Str::new("")
50    }
51}
52
53enum StrOwner {
54    None,
55    Static(&'static str),
56    Box(*mut str),
57    Shared(Arc<str>),
58}
59
60impl<'k> fmt::Debug for Str<'k> {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        fmt::Debug::fmt(self.get(), f)
63    }
64}
65
66impl<'k> fmt::Display for Str<'k> {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        fmt::Display::fmt(self.get(), f)
69    }
70}
71
72impl<'k> Clone for Str<'k> {
73    fn clone(&self) -> Self {
74        match self.owner {
75            StrOwner::Box(_) => Str::new_owned(unsafe { &*self.value }),
76            StrOwner::Shared(ref value) => Str::new_shared(value.clone()),
77            StrOwner::Static(owner) => Str {
78                value: self.value,
79                owner: StrOwner::Static(owner),
80                marker: PhantomData,
81            },
82            StrOwner::None => Str {
83                value: self.value,
84                owner: StrOwner::None,
85                marker: PhantomData,
86            },
87        }
88    }
89}
90
91impl<'k> Drop for Str<'k> {
92    fn drop(&mut self) {
93        if let StrOwner::Box(boxed) = self.owner {
94            drop(unsafe { Box::from_raw(boxed) });
95        }
96
97        // other cases handled normally
98    }
99}
100
101impl Str<'static> {
102    /// Create a new string from a value borrowed for `'static`.
103    pub const fn new(k: &'static str) -> Self {
104        Str {
105            value: k as *const str,
106            owner: StrOwner::Static(k),
107            marker: PhantomData,
108        }
109    }
110
111    /// Create a string from an owned value.
112    ///
113    /// Cloning the string will involve cloning the value.
114    pub fn new_owned(key: impl Into<Box<str>>) -> Self {
115        let value = key.into();
116
117        let raw = Box::into_raw(value);
118
119        Str {
120            value: raw as *const str,
121            owner: StrOwner::Box(raw),
122            marker: PhantomData,
123        }
124    }
125
126    /// Create a string from a shared value.
127    ///
128    /// Cloning the string will involve cloning the `Arc`, which may be cheaper than cloning the
129    /// value itself.
130    pub fn new_shared(key: impl Into<Arc<str>>) -> Self {
131        let value = key.into();
132
133        Str {
134            value: &*value as *const str,
135            owner: StrOwner::Shared(value),
136            marker: PhantomData,
137        }
138    }
139}
140
141impl<'k> Str<'k> {
142    /// Create a new string from a value borrowed for `'k`.
143    ///
144    /// The [`Str::new`] method should be preferred where possible.
145    pub const fn new_ref(k: &'k str) -> Str<'k> {
146        Str {
147            value: k as *const str,
148            owner: StrOwner::None,
149            marker: PhantomData,
150        }
151    }
152
153    /// Create a string from a potentially owned value.
154    ///
155    /// If the value is `Cow::Borrowed` then this method will defer to [`Str::new_ref`]. If the
156    /// value is `Cow::Owned` then this method will defer to [`Str::new_owned`].
157    pub fn new_cow_ref(key: Cow<'k, str>) -> Self {
158        match key {
159            Cow::Borrowed(key) => Str::new_ref(key),
160            Cow::Owned(key) => Str::new_owned(key),
161        }
162    }
163
164    /// Get a new [`Str`], borrowing data from this one.
165    pub const fn by_ref(&self) -> Str<'_> {
166        Str {
167            value: self.value,
168            owner: match self.owner {
169                StrOwner::Static(owner) => StrOwner::Static(owner),
170                _ => StrOwner::None,
171            },
172            marker: PhantomData,
173        }
174    }
175
176    /// Get a reference to the underlying value.
177    pub const fn get(&self) -> &str {
178        // NOTE: It's important here that the lifetime returned is not `'k`
179        // If it was it would be possible to return a `&'static str` from
180        // an owned value
181        // SAFETY: `self.value` is guaranteed to outlive the borrow of `self`
182        unsafe { &(*self.value) }
183    }
184
185    /// Try to get a reference to the underlying static value.
186    ///
187    /// If the string was created from [`Str::new`] and contains a `'static` value then this method
188    /// will return `Some`. Otherwise, this method will return `None`.
189    pub const fn get_static(&self) -> Option<&'static str> {
190        if let StrOwner::Static(owner) = self.owner {
191            Some(owner)
192        } else {
193            None
194        }
195    }
196
197    /// Get the underlying value as a potentially owned string.
198    ///
199    /// If the string contains a `'static` value then this method will return `Cow::Borrowed`.
200    /// Otherwise, it will return `Cow::Owned`.
201    pub fn to_cow(&self) -> Cow<'static, str> {
202        match self.owner {
203            StrOwner::Static(key) => Cow::Borrowed(key),
204            _ => Cow::Owned(self.get().to_owned()),
205        }
206    }
207
208    /// Get a new string, taking an owned copy of the data in this one.
209    ///
210    /// If the string contains a `'static` or `Arc` value then this method is cheap. In other cases
211    /// the underlying value will be passed through [`Str::new_shared`].
212    pub fn to_shared(&self) -> Str<'static> {
213        match self.owner {
214            StrOwner::Static(owner) => Str::new(owner),
215            StrOwner::Shared(ref owner) => Str::new_shared(owner.clone()),
216            _ => Str::new_shared(self.get()),
217        }
218    }
219
220    /// Get a new string, taking an owned copy of the data in this one.
221    ///
222    /// If the string contains a `'static` or `Arc` value then this method is cheap and doesn't
223    /// involve cloning. In other cases the underlying value will be passed through
224    /// [`Str::new_owned`].
225    pub fn to_owned(&self) -> Str<'static> {
226        match self.owner {
227            StrOwner::Static(owner) => Str::new(owner),
228            StrOwner::Shared(ref owner) => Str::new_shared(owner.clone()),
229            _ => Str::new_owned(self.get()),
230        }
231    }
232
233    /// Convert this string into an owned `String`.
234    ///
235    /// If the underlying value is already an owned string then this method will return it without
236    /// allocating.
237    pub fn into_string(self) -> String {
238        match self.owner {
239            StrOwner::Box(boxed) => {
240                // Ensure `Drop` doesn't run over this value
241                // and clean up the box we've just moved out of
242                std::mem::forget(self);
243                unsafe { Box::from_raw(boxed) }.into()
244            }
245            _ => self.get().to_owned(),
246        }
247    }
248}
249
250impl<'a> hash::Hash for Str<'a> {
251    fn hash<H: hash::Hasher>(&self, state: &mut H) {
252        self.get().hash(state)
253    }
254}
255
256impl std::ops::Deref for Str<'_> {
257    type Target = str;
258
259    fn deref(&self) -> &Self::Target {
260        self.get()
261    }
262}
263
264impl<'a, 'b> PartialEq<Str<'b>> for Str<'a> {
265    fn eq(&self, other: &Str<'b>) -> bool {
266        self.get() == other.get()
267    }
268}
269
270impl<'a> Eq for Str<'a> {}
271
272impl<'a> PartialEq<str> for Str<'a> {
273    fn eq(&self, other: &str) -> bool {
274        self.get() == other
275    }
276}
277
278impl<'a> PartialEq<Str<'a>> for str {
279    fn eq(&self, other: &Str<'a>) -> bool {
280        self == other.get()
281    }
282}
283
284impl<'a, 'b> PartialEq<&'b str> for Str<'a> {
285    fn eq(&self, other: &&'b str) -> bool {
286        self.get() == *other
287    }
288}
289
290impl<'b> PartialEq<Str<'b>> for &str {
291    fn eq(&self, other: &Str<'b>) -> bool {
292        *self == other.get()
293    }
294}
295
296impl<'a, 'b> PartialOrd<Str<'b>> for Str<'a> {
297    fn partial_cmp(&self, other: &Str<'b>) -> Option<core::cmp::Ordering> {
298        self.get().partial_cmp(other.get())
299    }
300}
301
302impl<'a> Ord for Str<'a> {
303    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
304        self.get().cmp(other.get())
305    }
306}
307
308impl<'k> Borrow<str> for Str<'k> {
309    fn borrow(&self) -> &str {
310        self.get()
311    }
312}
313
314impl<'k> AsRef<str> for Str<'k> {
315    fn as_ref(&self) -> &str {
316        self.get()
317    }
318}
319
320/// Convert a reference to a [`Str`].
321pub trait ToStr {
322    /// Perform the conversion.
323    fn to_str(&self) -> Str<'_>;
324}
325
326impl<T: ToStr + ?Sized> ToStr for &T {
327    fn to_str(&self) -> Str<'_> {
328        (**self).to_str()
329    }
330}
331
332impl<'k> ToStr for Str<'k> {
333    fn to_str(&self) -> Str<'_> {
334        self.by_ref()
335    }
336}
337
338impl ToStr for str {
339    fn to_str(&self) -> Str<'_> {
340        Str::new_ref(self)
341    }
342}
343
344impl ToStr for String {
345    fn to_str(&self) -> Str<'_> {
346        Str::new_ref(self)
347    }
348}
349
350impl ToStr for Box<str> {
351    fn to_str(&self) -> Str<'_> {
352        Str::new_ref(self)
353    }
354}
355
356impl ToStr for Arc<str> {
357    fn to_str(&self) -> Str<'_> {
358        Str::new_shared(self.clone())
359    }
360}
361
362impl From<String> for Str<'static> {
363    fn from(value: String) -> Self {
364        Str::new_owned(value)
365    }
366}
367
368impl From<Box<str>> for Str<'static> {
369    fn from(value: Box<str>) -> Self {
370        Str::new_owned(value)
371    }
372}
373
374impl From<Arc<str>> for Str<'static> {
375    fn from(value: Arc<str>) -> Self {
376        Str::new_shared(value)
377    }
378}
379
380impl<'k> From<&'k String> for Str<'k> {
381    fn from(value: &'k String) -> Self {
382        Str::new_ref(value)
383    }
384}
385
386impl<'k> From<Str<'k>> for String {
387    fn from(value: Str<'k>) -> String {
388        value.into_string()
389    }
390}
391
392impl<'a> From<&'a str> for Str<'a> {
393    fn from(value: &'a str) -> Self {
394        Str::new_ref(value)
395    }
396}
397
398impl<'a, 'b> From<&'a Str<'b>> for Str<'a> {
399    fn from(value: &'a Str<'b>) -> Self {
400        value.by_ref()
401    }
402}