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())
41                .map(Self)
42                .map_err(|_| Error::InvalidUtf8)
43        }
44    }
45
46    /// Extracts a string slice containing the entire string.
47    #[inline]
48    pub fn as_str(&self) -> &str {
49        self.0.as_str()
50    }
51
52    /// Returns a byte slice of this `String`'s contents.
53    #[inline]
54    pub fn as_bytes(&self) -> &[u8] {
55        self.0.as_bytes()
56    }
57
58    /// Returns the length of this `String`, in bytes.
59    #[inline]
60    pub fn len(&self) -> usize {
61        self.0.len()
62    }
63
64    /// Returns `true` if this `String` has a length of zero.
65    #[inline]
66    pub fn is_empty(&self) -> bool {
67        self.0.is_empty()
68    }
69
70    /// Appends a given string slice onto the end of this `String`.
71    ///
72    /// Returns `Ok(())` if successful, or `Err` if capacity would be exceeded.
73    #[inline]
74    pub fn push_str(&mut self, s: &str) -> Result<()> {
75        #[cfg(feature = "alloc")]
76        {
77            let new_len = self.0.len().saturating_add(s.len());
78            if new_len > N {
79                return Err(Error::CapacityExceeded);
80            }
81            self.0.push_str(s);
82            Ok(())
83        }
84        #[cfg(not(feature = "alloc"))]
85        {
86            self.0.push_str(s).map_err(|_| Error::CapacityExceeded)
87        }
88    }
89}
90
91impl<const N: usize> AsRef<str> for String<N> {
92    #[inline]
93    fn as_ref(&self) -> &str {
94        self.as_str()
95    }
96}
97
98impl<'a, const N: usize> TryFrom<&'a str> for String<N> {
99    type Error = Error;
100
101    #[inline]
102    fn try_from(s: &'a str) -> Result<Self> {
103        if s.len() > N {
104            return Err(Error::CapacityExceeded);
105        }
106        #[cfg(feature = "alloc")]
107        {
108            Ok(Self(alloc::string::String::from(s)))
109        }
110        #[cfg(not(feature = "alloc"))]
111        {
112            Inner::try_from(s)
113                .map(Self)
114                .map_err(|_| Error::CapacityExceeded)
115        }
116    }
117}
118
119impl<const N: usize> Default for String<N> {
120    #[inline]
121    fn default() -> Self {
122        #[cfg(feature = "alloc")]
123        {
124            Self(Inner::with_capacity(N))
125        }
126        #[cfg(not(feature = "alloc"))]
127        {
128            Self(Inner::new())
129        }
130    }
131}
132
133impl<const N: usize> From<Inner<N>> for String<N> {
134    #[inline]
135    fn from(inner: Inner<N>) -> Self {
136        Self(inner)
137    }
138}
139
140impl<const N: usize> fmt::Display for String<N> {
141    #[inline]
142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143        self.0.fmt(f)
144    }
145}
146
147impl<const N: usize> hash::Hash for String<N> {
148    #[inline]
149    fn hash<H: hash::Hasher>(&self, state: &mut H) {
150        self.0.hash(state);
151    }
152}
153
154impl<const N1: usize, const N2: usize> PartialEq<String<N2>> for String<N1> {
155    #[inline]
156    fn eq(&self, other: &String<N2>) -> bool {
157        self.as_str() == other.as_str()
158    }
159}
160
161impl<const N: usize> PartialEq<str> for String<N> {
162    #[inline]
163    fn eq(&self, other: &str) -> bool {
164        self.as_str() == other
165    }
166}
167
168impl<const N: usize> Eq for String<N> {}
169
170impl<const N: usize> PartialOrd for String<N> {
171    #[inline]
172    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
173        Some(self.cmp(other))
174    }
175}
176
177impl<const N: usize> Ord for String<N> {
178    #[inline]
179    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
180        self.as_str().cmp(other.as_str())
181    }
182}
183
184impl<const N: usize> fmt::Write for String<N> {
185    #[inline]
186    fn write_str(&mut self, s: &str) -> fmt::Result {
187        self.push_str(s).map_err(|_| fmt::Error)
188    }
189}