osom_tools_runtime/
immutable_string.rs

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