Skip to main content

osom_lib_strings/immutable/
immutable_string.rs

1#![allow(clippy::cast_possible_truncation)]
2
3use core::{borrow::Borrow, fmt::Display, hash::Hash};
4
5use osom_lib_alloc::traits::Allocator;
6use osom_lib_arc::carc_array::CArcArray;
7use osom_lib_primitives::length::Length;
8use osom_lib_reprc::macros::reprc;
9use osom_lib_try_clone::TryClone;
10
11use crate::immutable::{ImmutableStringError, MaxReferencesExceededError, WeakImmutableString};
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///
16/// NOTE: the immutable string internally keeps one byte more than the specified data. This last
17/// byte is always set to `\0` and thus the string is suitable to use as a C-style string,
18/// by calling [`ImmutableString::as_c_str()`][ImmutableString::as_c_str] method.
19#[reprc]
20#[repr(transparent)]
21#[derive(Debug)]
22#[must_use]
23pub struct ImmutableString<TAllocator: Allocator> {
24    internal: CArcArray<u8, TAllocator>,
25}
26
27impl<TAllocator: Allocator> ImmutableString<TAllocator> {
28    #[inline(always)]
29    pub(crate) fn from_internal(internal: CArcArray<u8, TAllocator>) -> Self {
30        Self { internal }
31    }
32
33    /// Creates a new, empty [`ImmutableString`].
34    ///
35    /// This function allocates under the hood, since all immutable strings are backed by smart pointer.
36    ///
37    /// # Errors
38    ///
39    /// For details see [`ImmutableStringError`].
40    #[inline(always)]
41    pub fn empty() -> Result<Self, ImmutableStringError>
42    where
43        TAllocator: Default,
44    {
45        Self::empty_with_allocator(TAllocator::default())
46    }
47
48    /// Creates a new [`ImmutableString`] out of passed string slice.
49    ///
50    /// This function allocates under the hood, since all immutable strings are backed by smart pointer.
51    /// It also copies the input into the output's buffer.
52    ///
53    /// # Errors
54    ///
55    /// For details see [`ImmutableStringError`].
56    #[inline(always)]
57    pub fn from_str_slice(text: &str) -> Result<Self, ImmutableStringError>
58    where
59        TAllocator: Default,
60    {
61        Self::from_str_slice_and_allocator(text, TAllocator::default())
62    }
63
64    /// Creates a new [`ImmutableString`] out of passed string slice and allocator.
65    ///
66    /// This function allocates under the hood, since all immutable strings are backed by smart pointer.
67    /// It also copies the input into the output's buffer.
68    ///
69    /// # Errors
70    ///
71    /// For details see [`ImmutableStringError`].
72    #[inline]
73    pub fn from_str_slice_and_allocator(text: &str, allocator: TAllocator) -> Result<Self, ImmutableStringError> {
74        let text_len = Length::try_from_usize(text.len())?;
75        let mut builder = super::ImmutableStringBuilder::with_capacity_and_allocator(text_len, allocator)?;
76        builder.try_push(text)?;
77        let result = builder.build()?;
78        Ok(result)
79    }
80
81    /// Creates a new, empty [`ImmutableString`] with a given allocator.
82    ///
83    /// This function allocates under the hood, since all immutable strings are backed by smart pointer.
84    ///
85    /// # Errors
86    ///
87    /// For details see [`ImmutableStringError`].
88    #[inline(always)]
89    pub fn empty_with_allocator(allocator: TAllocator) -> Result<Self, ImmutableStringError> {
90        Self::from_str_slice_and_allocator("", allocator)
91    }
92
93    #[inline(always)]
94    #[must_use]
95    pub fn strong_count(&self) -> u32 {
96        CArcArray::strong_count(&self.internal)
97    }
98
99    #[inline(always)]
100    #[must_use]
101    pub fn weak_count(&self) -> u32 {
102        CArcArray::weak_count(&self.internal)
103    }
104
105    /// Compares the underlying raw pointers of the two strings.
106    ///
107    /// Useful for determining whether the two strings have the same
108    /// allocation, not only data.
109    #[inline]
110    #[must_use]
111    pub fn ptr_eq(&self, other: &Self) -> bool {
112        core::ptr::eq(CArcArray::data(&self.internal), CArcArray::data(&other.internal))
113    }
114
115    /// Returns the underlying string.
116    #[inline]
117    #[must_use]
118    pub fn as_str(&self) -> &str {
119        unsafe {
120            let slice = self.internal.as_ref();
121            core::str::from_utf8_unchecked(&slice[..slice.len() - 1])
122        }
123    }
124
125    /// Returns the underlying string as C-string.
126    ///
127    /// Meaning the string has an additional 0 at the end of the buffer. In particular,
128    /// the C-string returned by this method has length +1 compared to [`ImmutableString::as_str`]
129    /// call.
130    #[inline]
131    #[must_use]
132    pub fn as_c_str(&self) -> &str {
133        unsafe {
134            let slice = self.internal.as_ref();
135            core::str::from_utf8_unchecked(slice)
136        }
137    }
138
139    /// The length of the string.
140    #[inline]
141    pub fn length(&self) -> Length {
142        unsafe {
143            let slice = self.internal.as_ref();
144            Length::new_unchecked((slice.len() - 1) as u32)
145        }
146    }
147
148    /// Creates a new [`WeakImmutableString`] out of current.
149    ///
150    /// # Errors
151    ///
152    /// Returns [`MaxReferencesExceededError`] if the weak reference count is too high.
153    /// Cannot exceed [`osom_lib_arc::consts::MAX_REFERENCES`].
154    pub fn downgrade(&self) -> Result<WeakImmutableString<TAllocator>, MaxReferencesExceededError> {
155        let weak = CArcArray::downgrade(&self.internal)?;
156        Ok(WeakImmutableString::from_internal(weak))
157    }
158
159    /// Abandons current [`ImmutableString`].
160    ///
161    /// This function returns None if the underlying strong reference counter
162    /// is still positive. Otherwise it returns the final [`WeakImmutableString`]
163    /// reference.
164    #[inline]
165    #[must_use]
166    pub fn abandon(self) -> Option<WeakImmutableString<TAllocator>> {
167        let result = CArcArray::<u8, TAllocator>::abandon(self.internal)?;
168        Some(WeakImmutableString::from_internal(result))
169    }
170}
171
172impl<TAllocator: Allocator> TryClone for ImmutableString<TAllocator> {
173    type Error = MaxReferencesExceededError;
174
175    fn try_clone(&self) -> Result<Self, Self::Error> {
176        let internal = self.internal.try_clone()?;
177        Ok(Self { internal })
178    }
179}
180
181impl<TAllocator: Allocator> Clone for ImmutableString<TAllocator> {
182    fn clone(&self) -> Self {
183        self.try_clone()
184            .expect("ImmutableString strong reference count is too high.")
185    }
186}
187
188impl<TAllocator: Allocator> AsRef<str> for ImmutableString<TAllocator> {
189    fn as_ref(&self) -> &str {
190        self.as_str()
191    }
192}
193
194impl<TAllocator: Allocator> Borrow<str> for ImmutableString<TAllocator> {
195    fn borrow(&self) -> &str {
196        self.as_str()
197    }
198}
199
200impl<TAllocator: Allocator> Hash for ImmutableString<TAllocator> {
201    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
202        self.as_str().hash(state);
203    }
204}
205
206impl<TAllocator: Allocator> PartialEq for ImmutableString<TAllocator> {
207    fn eq(&self, other: &Self) -> bool {
208        self.internal == other.internal
209    }
210}
211
212impl<TAllocator: Allocator> Eq for ImmutableString<TAllocator> {}
213
214impl<TAllocator: Allocator> Display for ImmutableString<TAllocator> {
215    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
216        write!(f, "{}", self.as_str())
217    }
218}