tensor4all_core/
smallstring.rs1pub trait SmallChar: Copy + Default + Ord + Eq + std::hash::Hash + std::fmt::Debug {
9 const ZERO: Self;
11
12 fn from_char(c: char) -> Option<Self>;
15
16 fn to_char(self) -> char;
18}
19
20impl SmallChar for u16 {
21 const ZERO: Self = 0;
22
23 fn from_char(c: char) -> Option<Self> {
24 let code = c as u32;
26 if code <= 0xFFFF {
27 Some(code as u16)
28 } else {
29 None }
31 }
32
33 fn to_char(self) -> char {
34 char::from_u32(self as u32).unwrap_or('\u{FFFD}')
36 }
37}
38
39impl SmallChar for char {
40 const ZERO: Self = '\0';
41
42 fn from_char(c: char) -> Option<Self> {
43 Some(c)
44 }
45
46 fn to_char(self) -> char {
47 self
48 }
49}
50
51#[derive(Debug, Clone, Copy)]
78pub struct SmallString<const MAX_LEN: usize, C: SmallChar = u16> {
79 data: [C; MAX_LEN],
80 len: usize, }
82
83#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
85pub enum SmallStringError {
86 #[error("String too long ({actual} > {max})")]
88 TooLong {
89 actual: usize,
91 max: usize,
93 },
94 #[error("Invalid character: {char_value:?}")]
96 InvalidChar {
97 char_value: char,
99 },
100}
101
102impl<const MAX_LEN: usize, C: SmallChar> SmallString<MAX_LEN, C> {
103 pub fn new() -> Self {
105 Self {
106 data: [C::ZERO; MAX_LEN],
107 len: 0,
108 }
109 }
110
111 #[allow(clippy::should_implement_trait)]
119 pub fn from_str(s: &str) -> Result<Self, SmallStringError> {
125 let mut data = [C::ZERO; MAX_LEN];
126 let mut len = 0;
127
128 for ch in s.chars() {
129 if len >= MAX_LEN {
130 let actual = len + 1 + s.chars().skip(len + 1).count();
132 return Err(SmallStringError::TooLong {
133 actual,
134 max: MAX_LEN,
135 });
136 }
137 data[len] = C::from_char(ch).ok_or(SmallStringError::InvalidChar { char_value: ch })?;
138 len += 1;
139 }
140
141 Ok(Self { data, len })
142 }
143
144 pub fn as_str(&self) -> String {
146 self.data[..self.len].iter().map(|c| c.to_char()).collect()
147 }
148
149 pub fn is_empty(&self) -> bool {
151 self.len == 0
152 }
153
154 pub fn len(&self) -> usize {
156 self.len
157 }
158
159 pub fn capacity(&self) -> usize {
161 MAX_LEN
162 }
163
164 pub fn get(&self, index: usize) -> Option<char> {
166 if index < self.len {
167 Some(self.data[index].to_char())
168 } else {
169 None
170 }
171 }
172
173 pub fn as_slice(&self) -> &[C] {
175 &self.data[..self.len]
176 }
177}
178
179impl<const MAX_LEN: usize, C: SmallChar> Default for SmallString<MAX_LEN, C> {
180 fn default() -> Self {
181 Self::new()
182 }
183}
184
185impl<const MAX_LEN: usize, C: SmallChar> PartialEq for SmallString<MAX_LEN, C> {
186 fn eq(&self, other: &Self) -> bool {
187 self.len == other.len && self.data[..self.len] == other.data[..other.len]
188 }
189}
190
191impl<const MAX_LEN: usize, C: SmallChar> Eq for SmallString<MAX_LEN, C> {}
192
193impl<const MAX_LEN: usize, C: SmallChar> std::hash::Hash for SmallString<MAX_LEN, C> {
194 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
195 self.data[..self.len].hash(state);
196 }
197}
198
199impl<const MAX_LEN: usize, C: SmallChar> PartialOrd for SmallString<MAX_LEN, C> {
200 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
201 Some(self.cmp(other))
202 }
203}
204
205impl<const MAX_LEN: usize, C: SmallChar> Ord for SmallString<MAX_LEN, C> {
206 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
207 self.data[..self.len].cmp(&other.data[..other.len])
208 }
209}
210
211impl<const MAX_LEN: usize, C: SmallChar> std::fmt::Display for SmallString<MAX_LEN, C> {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 write!(f, "{}", self.as_str())
214 }
215}
216
217#[cfg(test)]
218mod tests;