osom_lib_strings/
immutable_string.rs

1//! Holds [`ImmutableString`] struct and related tools.
2
3use core::mem::ManuallyDrop;
4
5use osom_lib_alloc::Allocator;
6use osom_lib_arrays::{ImmutableArray, ImmutableWeakArray, errors::ArrayConstructionError};
7use osom_lib_primitives::Length;
8
9#[cfg(feature = "std_alloc")]
10use osom_lib_alloc::StdAllocator;
11
12/// Represents an error that occurs when constructing new [`ImmutableString`].
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14#[must_use]
15#[repr(u8)]
16pub enum ImmutableStringConstructionError {
17    /// The allocator failed to allocate memory.
18    AllocationError,
19
20    /// The passed array is too long, it exceeds [`MAX_LENGTH`][`ImmutableString::MAX_LENGTH`].
21    StringTooLong,
22}
23
24impl From<ArrayConstructionError> for ImmutableStringConstructionError {
25    fn from(error: ArrayConstructionError) -> Self {
26        match error {
27            ArrayConstructionError::AllocationError => ImmutableStringConstructionError::AllocationError,
28            ArrayConstructionError::ArrayTooLong => ImmutableStringConstructionError::StringTooLong,
29        }
30    }
31}
32
33/// Represents an immutable string, which is stored behind ref counters.
34/// This is a thin wrapper around [`ImmutableArray<u8>`], and is thread
35/// safe as well.
36///
37/// Cloning and moving around are very cheap.
38///
39/// # Notes
40///
41/// In order to build [`ImmutableString`] incrementally, use
42/// [`ImmutableArrayBuilder<u8>`][`osom_lib_arrays::ImmutableArrayBuilder<u8>`]
43/// and convert the final [`ImmutableArray<u8>`] to [`ImmutableString`] either safely
44/// (with UTF-8 validation) or unsafely (without validation).
45#[derive(Clone)]
46#[repr(transparent)]
47#[must_use]
48pub struct ImmutableString<
49    #[cfg(feature = "std_alloc")] TAllocator = StdAllocator,
50    #[cfg(not(feature = "std_alloc"))] TAllocator,
51> where
52    TAllocator: Allocator,
53{
54    internal: ManuallyDrop<ImmutableArray<u8, TAllocator>>,
55}
56
57unsafe impl<TAllocator: Allocator> Send for ImmutableString<TAllocator> {}
58unsafe impl<TAllocator: Allocator> Sync for ImmutableString<TAllocator> {}
59
60/// Represents a weak reference to an [`ImmutableString`].
61/// Analogously to how [`ImmutableWeakArray`] is a weak
62/// reference to [`ImmutableArray`].
63#[derive(Clone)]
64#[repr(transparent)]
65pub struct ImmutableWeakString<
66    #[cfg(feature = "std_alloc")] TAllocator = StdAllocator,
67    #[cfg(not(feature = "std_alloc"))] TAllocator,
68> where
69    TAllocator: Allocator,
70{
71    internal: ManuallyDrop<ImmutableWeakArray<u8, TAllocator>>,
72}
73
74impl<TAllocator: Allocator> ImmutableWeakString<TAllocator> {
75    fn from_internal(internal: ImmutableWeakArray<u8, TAllocator>) -> Self {
76        Self {
77            internal: ManuallyDrop::new(internal),
78        }
79    }
80
81    /// Upgrades the [`ImmutableWeakString`] to an [`ImmutableString`].
82    ///
83    /// Returns `None` if the [`ImmutableWeakString`] is no longer valid, i.e.
84    /// the underlying memory has been deallocated. Otherwise returns
85    /// a new strong
86    #[inline(always)]
87    pub fn upgrade(&self) -> Option<ImmutableString<TAllocator>> {
88        let internal = self.internal.upgrade()?;
89        Some(ImmutableString::from_internal(internal))
90    }
91
92    /// Returns the number of strong references to the [`ImmutableWeakString`].
93    #[inline(always)]
94    #[must_use]
95    pub fn strong_count(instance: &Self) -> usize {
96        instance.internal.strong_count()
97    }
98
99    /// Returns the number of weak references to the [`ImmutableWeakString`].
100    #[inline(always)]
101    #[must_use]
102    pub fn weak_count(instance: &Self) -> usize {
103        instance.internal.weak_count()
104    }
105
106    /// Releases the [`ImmutableWeakString`]. If this was the last weak reference,
107    /// it will deallocated the underlying memory and return `true`.
108    /// Otherwise it will just return `false`.
109    #[inline(always)]
110    pub fn release(mut self) -> bool {
111        let internal = unsafe { ManuallyDrop::take(&mut self.internal) };
112        let val = internal.release();
113        core::mem::forget(self);
114        val
115    }
116}
117
118impl<TAllocator: Allocator> Drop for ImmutableWeakString<TAllocator> {
119    fn drop(&mut self) {
120        unsafe { ManuallyDrop::drop(&mut self.internal) };
121    }
122}
123
124impl<TAllocator: Allocator> ImmutableString<TAllocator> {
125    fn from_internal(internal: ImmutableArray<u8, TAllocator>) -> Self {
126        Self {
127            internal: ManuallyDrop::new(internal),
128        }
129    }
130
131    pub const MAX_LENGTH: usize = ImmutableArray::<u8, TAllocator>::MAX_LENGTH;
132
133    /// Constructs a new [`ImmutableString`] from a string with default allocator.
134    /// It copies the string into the new [`ImmutableString`].
135    ///
136    /// # Errors
137    ///
138    /// For details see [`ImmutableStringConstructionError`].
139    pub fn new(text: &str) -> Result<Self, ImmutableStringConstructionError> {
140        Self::with_allocator(text, TAllocator::default())
141    }
142
143    /// Constructs a new [`ImmutableString`] from a string and an allocator.
144    /// It copies the string into the new [`ImmutableString`].
145    ///
146    /// # Errors
147    ///
148    /// For details see [`ImmutableStringConstructionError`].
149    pub fn with_allocator(text: &str, allocator: TAllocator) -> Result<Self, ImmutableStringConstructionError> {
150        let array = ImmutableArray::from_slice_with_allocator(text.as_bytes(), allocator)?;
151        Ok(Self {
152            internal: ManuallyDrop::new(array),
153        })
154    }
155
156    #[inline(always)]
157    #[must_use]
158    pub fn as_str(&self) -> &str {
159        let slice = self.internal.as_slice();
160        unsafe { core::str::from_utf8_unchecked(slice) }
161    }
162
163    /// Downgrades the [`ImmutableString`] to a [`ImmutableWeakString`] and increments the internal weak counter.
164    #[inline(always)]
165    pub fn downgrade(instance: &Self) -> ImmutableWeakString<TAllocator> {
166        ImmutableWeakString::from_internal(ImmutableArray::downgrade(&instance.internal))
167    }
168
169    /// Returns the number of strong references to the [`ImmutableString`].
170    #[inline(always)]
171    #[must_use]
172    pub fn strong_count(instance: &Self) -> usize {
173        ImmutableArray::strong_count(&instance.internal)
174    }
175
176    /// Returns the number of weak references to the [`ImmutableString`].
177    #[inline(always)]
178    #[must_use]
179    pub fn weak_count(instance: &Self) -> usize {
180        ImmutableArray::weak_count(&instance.internal)
181    }
182
183    /// Returns the length of the [`ImmutableString`].
184    #[inline(always)]
185    pub fn len(&self) -> Length {
186        self.internal.len()
187    }
188
189    /// Returns `true` if the [`ImmutableString`] is empty, `false` otherwise.
190    #[inline(always)]
191    #[must_use]
192    pub fn is_empty(&self) -> bool {
193        self.len().value() == 0
194    }
195
196    /// Releases the [`ImmutableString`]. If this was the last strong reference,
197    /// it will return a [`ImmutableWeakString`]. Otherwise it will return `None`.
198    pub fn release(mut instance: Self) -> Option<ImmutableWeakString<TAllocator>> {
199        let internal = unsafe { ManuallyDrop::take(&mut instance.internal) };
200        let weak = ImmutableArray::release(internal)?;
201        core::mem::forget(instance);
202        Some(ImmutableWeakString::from_internal(weak))
203    }
204
205    /// Constructs a new [`ImmutableString`] from an [`ImmutableArray<u8>`].
206    ///
207    /// # Safety
208    ///
209    /// This method does not check if the array is a valid UTF-8 string.
210    #[inline(always)]
211    pub unsafe fn from_unchecked(value: ImmutableArray<u8, TAllocator>) -> Self {
212        Self {
213            internal: ManuallyDrop::new(value),
214        }
215    }
216}
217
218impl<TAllocator: Allocator> Drop for ImmutableString<TAllocator> {
219    fn drop(&mut self) {
220        unsafe { ManuallyDrop::drop(&mut self.internal) };
221    }
222}
223
224impl<TAllocator: Allocator> core::ops::Deref for ImmutableString<TAllocator> {
225    type Target = str;
226
227    fn deref(&self) -> &Self::Target {
228        self.as_str()
229    }
230}
231
232impl<TAllocator: Allocator> core::fmt::Debug for ImmutableString<TAllocator> {
233    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
234        f.debug_struct("ImmutableWeakString")
235            .field("strong_count", &Self::strong_count(self))
236            .field("weak_count", &Self::weak_count(self))
237            .field("len", &self.len())
238            .field("capacity", &self.internal.capacity())
239            .finish()
240    }
241}
242
243impl<TAllocator1: Allocator, TAllocator2: Allocator> core::cmp::PartialEq<ImmutableString<TAllocator1>>
244    for ImmutableString<TAllocator2>
245{
246    fn eq(&self, other: &ImmutableString<TAllocator1>) -> bool {
247        self.as_str() == other.as_str()
248    }
249}
250
251impl<TAllocator: Allocator> core::cmp::Eq for ImmutableString<TAllocator> {}
252
253impl<TAllocator: Allocator> core::hash::Hash for ImmutableString<TAllocator> {
254    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
255        self.internal.hash(state);
256    }
257}
258
259impl<TAllocator: Allocator> TryFrom<ImmutableArray<u8, TAllocator>> for ImmutableString<TAllocator> {
260    type Error = core::str::Utf8Error;
261
262    fn try_from(value: ImmutableArray<u8, TAllocator>) -> Result<Self, Self::Error> {
263        let _ = core::str::from_utf8(value.as_slice())?;
264        Ok(Self {
265            internal: ManuallyDrop::new(value),
266        })
267    }
268}
269
270impl<TAllocator: Allocator> From<ImmutableString<TAllocator>> for ImmutableArray<u8, TAllocator> {
271    fn from(mut value: ImmutableString<TAllocator>) -> Self {
272        let internal = unsafe { ManuallyDrop::take(&mut value.internal) };
273        core::mem::forget(value);
274        internal
275    }
276}