Skip to main content

sigma_bounded/
string.rs

1//! A UTF-8–encoded, growable string.
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6use core::{fmt, hash, str};
7
8use crate::error::{Error, Result};
9use crate::vec::Vec;
10
11#[cfg(feature = "alloc")]
12type Inner<const N: usize> = alloc::string::String;
13#[cfg(not(feature = "alloc"))]
14type Inner<const N: usize> = heapless::String<N>;
15
16/// A UTF-8–encoded, growable string.
17///
18/// When `heapless` feature is enabled, this is wrapper around `heapless::String`. Otherwise, this
19/// is a wrapper around `alloc::string::String`, with logical length capped at `N` bytes.
20#[derive(Clone, Debug)]
21pub struct String<const N: usize>(Inner<N>);
22
23impl<const N: usize> String<N> {
24    /// Constructs a new, empty `String` with a capacity of `N` bytes.
25    #[inline]
26    pub fn new() -> Self {
27        Self::default()
28    }
29
30    /// Convert UTF-8 bytes into a `String`.
31    #[inline]
32    pub fn from_utf8(vec: Vec<u8, N>) -> Result<Self> {
33        #[cfg(feature = "alloc")]
34        {
35            let utf8_str = str::from_utf8(vec.as_slice()).map_err(|_| Error::InvalidUtf8)?;
36            Ok(Self(alloc::string::String::from(utf8_str)))
37        }
38        #[cfg(not(feature = "alloc"))]
39        {
40            Inner::from_utf8(vec.into_inner()).map(Self).map_err(|_| Error::InvalidUtf8)
41        }
42    }
43
44    /// Extracts a string slice containing the entire string.
45    #[inline]
46    pub fn as_str(&self) -> &str {
47        self.0.as_str()
48    }
49
50    /// Returns a byte slice of this `String`'s contents.
51    #[inline]
52    pub fn as_bytes(&self) -> &[u8] {
53        self.0.as_bytes()
54    }
55
56    /// Returns the length of this `String`, in bytes.
57    #[inline]
58    pub fn len(&self) -> usize {
59        self.0.len()
60    }
61
62    /// Returns `true` if this `String` has a length of zero.
63    #[inline]
64    pub fn is_empty(&self) -> bool {
65        self.0.is_empty()
66    }
67
68    /// Appends a given string slice onto the end of this `String`.
69    ///
70    /// Returns `Ok(())` if successful, or `Err` if capacity would be exceeded.
71    #[inline]
72    pub fn push_str(&mut self, s: &str) -> Result<()> {
73        #[cfg(feature = "alloc")]
74        {
75            let new_len = self.0.len().saturating_add(s.len());
76            if new_len > N {
77                return Err(Error::CapacityExceeded);
78            }
79            self.0.push_str(s);
80            Ok(())
81        }
82        #[cfg(not(feature = "alloc"))]
83        {
84            self.0.push_str(s).map_err(|_| Error::CapacityExceeded)
85        }
86    }
87}
88
89impl<const N: usize> AsRef<str> for String<N> {
90    #[inline]
91    fn as_ref(&self) -> &str {
92        self.as_str()
93    }
94}
95
96impl<'a, const N: usize> TryFrom<&'a str> for String<N> {
97    type Error = Error;
98
99    #[inline]
100    fn try_from(s: &'a str) -> Result<Self> {
101        if s.len() > N {
102            return Err(Error::CapacityExceeded);
103        }
104        #[cfg(feature = "alloc")]
105        {
106            Ok(Self(alloc::string::String::from(s)))
107        }
108        #[cfg(not(feature = "alloc"))]
109        {
110            Inner::try_from(s).map(Self).map_err(|_| Error::CapacityExceeded)
111        }
112    }
113}
114
115impl<const N: usize> Default for String<N> {
116    #[inline]
117    fn default() -> Self {
118        #[cfg(feature = "alloc")]
119        {
120            Self(Inner::with_capacity(N))
121        }
122        #[cfg(not(feature = "alloc"))]
123        {
124            Self(Inner::new())
125        }
126    }
127}
128
129impl<const N: usize> From<Inner<N>> for String<N> {
130    #[inline]
131    fn from(inner: Inner<N>) -> Self {
132        Self(inner)
133    }
134}
135
136impl<const N: usize> fmt::Display for String<N> {
137    #[inline]
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        self.0.fmt(f)
140    }
141}
142
143impl<const N: usize> hash::Hash for String<N> {
144    #[inline]
145    fn hash<H: hash::Hasher>(&self, state: &mut H) {
146        self.0.hash(state);
147    }
148}
149
150impl<const N1: usize, const N2: usize> PartialEq<String<N2>> for String<N1> {
151    #[inline]
152    fn eq(&self, other: &String<N2>) -> bool {
153        self.as_str() == other.as_str()
154    }
155}
156
157impl<const N: usize> PartialEq<str> for String<N> {
158    #[inline]
159    fn eq(&self, other: &str) -> bool {
160        self.as_str() == other
161    }
162}
163
164impl<const N: usize> Eq for String<N> {}
165
166impl<const N: usize> PartialOrd for String<N> {
167    #[inline]
168    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
169        Some(self.cmp(other))
170    }
171}
172
173impl<const N: usize> Ord for String<N> {
174    #[inline]
175    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
176        self.as_str().cmp(other.as_str())
177    }
178}
179
180impl<const N: usize> fmt::Write for String<N> {
181    #[inline]
182    fn write_str(&mut self, s: &str) -> fmt::Result {
183        self.push_str(s).map_err(|_| fmt::Error)
184    }
185}