Skip to main content

osom_lib_strings/immutable/
immutable_string.rs

1use core::{
2    borrow::Borrow,
3    hash::Hash,
4    sync::atomic::{Ordering, fence},
5};
6
7use osom_lib_alloc::traits::Allocator;
8use osom_lib_primitives::length::Length;
9use osom_lib_reprc::macros::reprc;
10
11use crate::immutable::{ImmutableStringError, WeakImmutableString, internal_string::InternalString};
12
13/// This struct is a smart pointer around a string. Internally keeps reference counters
14/// for both strong and weak references. Cloning the struct is therefore cheap.
15#[reprc]
16#[repr(transparent)]
17#[derive(Debug)]
18pub struct ImmutableString<TAllocator: Allocator> {
19    internal: InternalString<TAllocator>,
20}
21
22impl<TAllocator: Allocator> ImmutableString<TAllocator> {
23    #[inline(always)]
24    pub(crate) fn from_internal(internal: InternalString<TAllocator>) -> Self {
25        Self { internal }
26    }
27
28    /// Creates a new, empty [`ImmutableString`].
29    ///
30    /// This function allocates under the hood, since all immutable strings are backed by smart pointer.
31    ///
32    /// # Errors
33    ///
34    /// For details see [`ImmutableStringError`].
35    #[inline(always)]
36    pub fn empty() -> Result<Self, ImmutableStringError> {
37        Self::empty_with_allocator(TAllocator::default())
38    }
39
40    /// Creates a new [`ImmutableString`] out of passed string slice.
41    ///
42    /// This function allocates under the hood, since all immutable strings are backed by smart pointer.
43    /// It also copies the input into the output's buffer.
44    ///
45    /// # Errors
46    ///
47    /// For details see [`ImmutableStringError`].
48    #[inline(always)]
49    pub fn from_str_slice(text: &str) -> Result<Self, ImmutableStringError> {
50        Self::from_str_slice_and_allocator(text, TAllocator::default())
51    }
52
53    /// Creates a new [`ImmutableString`] out of passed string slice and allocator.
54    ///
55    /// This function allocates under the hood, since all immutable strings are backed by smart pointer.
56    /// It also copies the input into the output's buffer.
57    ///
58    /// # Errors
59    ///
60    /// For details see [`ImmutableStringError`].
61    #[inline]
62    pub fn from_str_slice_and_allocator(text: &str, allocator: TAllocator) -> Result<Self, ImmutableStringError> {
63        let text_len = Length::try_from_usize(text.len())?;
64        let mut builder = super::ImmutableStringBuilder::with_capacity_and_allocator(text_len, allocator)?;
65        builder.try_push(text)?;
66        let result = builder.build()?;
67        Ok(result)
68    }
69
70    /// Creates a new, empty [`ImmutableString`] with a given allocator.
71    ///
72    /// This function allocates under the hood, since all immutable strings are backed by smart pointer.
73    ///
74    /// # Errors
75    ///
76    /// For details see [`ImmutableStringError`].
77    #[inline(always)]
78    pub fn empty_with_allocator(allocator: TAllocator) -> Result<Self, ImmutableStringError> {
79        let internal = InternalString::with_allocator_and_capacity(Length::ZERO, allocator)?;
80        let result = Self::from_internal(internal);
81        Ok(result)
82    }
83
84    #[inline(always)]
85    pub fn strong_count(&self) -> u32 {
86        self.internal.strong().load(Ordering::Relaxed)
87    }
88
89    #[inline(always)]
90    pub fn weak_count(&self) -> u32 {
91        self.internal.weak().load(Ordering::Relaxed)
92    }
93
94    /// Returns the underlying string.
95    #[inline]
96    pub const fn as_str(&self) -> &str {
97        unsafe {
98            let slice = core::slice::from_raw_parts(self.internal.data_start(), self.internal.length().as_usize() - 1);
99            core::str::from_utf8_unchecked(slice)
100        }
101    }
102
103    /// Returns the underlying string as C-string.
104    ///
105    /// Meaning the string has an additional 0 at the end of the buffer. In particular,
106    /// the C-string returned by this method has length +1 compared to [`ImmutableString::as_str`]
107    /// call.
108    #[inline]
109    pub const fn as_c_str(&self) -> &str {
110        unsafe {
111            let slice = core::slice::from_raw_parts(self.internal.data_start(), self.internal.length().as_usize());
112            core::str::from_utf8_unchecked(slice)
113        }
114    }
115
116    /// The length of the string.
117    #[inline]
118    pub const fn length(&self) -> Length {
119        unsafe { Length::new_unchecked(self.internal.length().as_u32() - 1) }
120    }
121
122    /// Creates a new [`WeakImmutableString`] out of current.
123    #[inline]
124    pub fn downgrade(&self) -> WeakImmutableString<TAllocator> {
125        let internal_clone = self.internal.clone();
126        internal_clone.weak().fetch_add(1, Ordering::Relaxed);
127        WeakImmutableString::from_internal(internal_clone)
128    }
129
130    /// Abandons current [`ImmutableString`].
131    ///
132    /// This function returns None if the underlying strong reference counter
133    /// is still positive. Otherwise it returns the final [`WeakImmutableString`]
134    /// reference.
135    #[inline]
136    pub fn abandon(mut self) -> Option<WeakImmutableString<TAllocator>> {
137        let result = self.internal_abandon();
138        core::mem::forget(self);
139        result
140    }
141
142    fn internal_abandon(&mut self) -> Option<WeakImmutableString<TAllocator>> {
143        let internal = unsafe { core::ptr::read(&raw const self.internal) };
144        let prev = internal.strong().fetch_sub(1, Ordering::Release);
145        if prev > 1 {
146            return None;
147        }
148
149        // Synchronize with all prior Release decrements before deallocating.
150        fence(Ordering::Acquire);
151        Some(WeakImmutableString::from_internal(internal))
152    }
153}
154
155impl<TAllocator: Allocator> Drop for ImmutableString<TAllocator> {
156    fn drop(&mut self) {
157        let _ = self.internal_abandon();
158    }
159}
160
161impl<TAllocator: Allocator> Clone for ImmutableString<TAllocator> {
162    fn clone(&self) -> Self {
163        let internal_clone = self.internal.clone();
164        internal_clone.strong().fetch_add(1, Ordering::Relaxed);
165        Self {
166            internal: internal_clone,
167        }
168    }
169}
170
171impl<TAllocator: Allocator> AsRef<str> for ImmutableString<TAllocator> {
172    fn as_ref(&self) -> &str {
173        self.as_str()
174    }
175}
176
177impl<TAllocator: Allocator> Borrow<str> for ImmutableString<TAllocator> {
178    fn borrow(&self) -> &str {
179        self.as_str()
180    }
181}
182
183impl<TAllocator: Allocator> Hash for ImmutableString<TAllocator> {
184    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
185        self.as_str().hash(state);
186    }
187}
188
189impl<TAllocator: Allocator, TRight: AsRef<str>> PartialEq<TRight> for ImmutableString<TAllocator> {
190    fn eq(&self, other: &TRight) -> bool {
191        self.as_str() == other.as_ref()
192    }
193}
194
195impl<TAllocator: Allocator> Eq for ImmutableString<TAllocator> {}