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)]
77pub struct SmallString<const MAX_LEN: usize, C: SmallChar = u16> {
78 data: [C; MAX_LEN],
79 len: usize, }
81
82#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
84pub enum SmallStringError {
85 #[error("String too long ({actual} > {max})")]
87 TooLong {
88 actual: usize,
90 max: usize,
92 },
93 #[error("Invalid character: {char_value:?}")]
95 InvalidChar {
96 char_value: char,
98 },
99}
100
101impl<const MAX_LEN: usize, C: SmallChar> SmallString<MAX_LEN, C> {
102 pub fn new() -> Self {
104 Self {
105 data: [C::ZERO; MAX_LEN],
106 len: 0,
107 }
108 }
109
110 #[allow(clippy::should_implement_trait)]
118 pub fn from_str(s: &str) -> Result<Self, SmallStringError> {
119 let mut data = [C::ZERO; MAX_LEN];
120 let mut len = 0;
121
122 for ch in s.chars() {
123 if len >= MAX_LEN {
124 let actual = len + 1 + s.chars().skip(len + 1).count();
126 return Err(SmallStringError::TooLong {
127 actual,
128 max: MAX_LEN,
129 });
130 }
131 data[len] = C::from_char(ch).ok_or(SmallStringError::InvalidChar { char_value: ch })?;
132 len += 1;
133 }
134
135 Ok(Self { data, len })
136 }
137
138 pub fn as_str(&self) -> String {
140 self.data[..self.len].iter().map(|c| c.to_char()).collect()
141 }
142
143 pub fn is_empty(&self) -> bool {
145 self.len == 0
146 }
147
148 pub fn len(&self) -> usize {
150 self.len
151 }
152
153 pub fn capacity(&self) -> usize {
155 MAX_LEN
156 }
157
158 pub fn get(&self, index: usize) -> Option<char> {
160 if index < self.len {
161 Some(self.data[index].to_char())
162 } else {
163 None
164 }
165 }
166
167 pub fn as_slice(&self) -> &[C] {
169 &self.data[..self.len]
170 }
171}
172
173impl<const MAX_LEN: usize, C: SmallChar> Default for SmallString<MAX_LEN, C> {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179impl<const MAX_LEN: usize, C: SmallChar> PartialEq for SmallString<MAX_LEN, C> {
180 fn eq(&self, other: &Self) -> bool {
181 self.len == other.len && self.data[..self.len] == other.data[..other.len]
182 }
183}
184
185impl<const MAX_LEN: usize, C: SmallChar> Eq for SmallString<MAX_LEN, C> {}
186
187impl<const MAX_LEN: usize, C: SmallChar> std::hash::Hash for SmallString<MAX_LEN, C> {
188 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
189 self.data[..self.len].hash(state);
190 }
191}
192
193impl<const MAX_LEN: usize, C: SmallChar> PartialOrd for SmallString<MAX_LEN, C> {
194 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
195 Some(self.cmp(other))
196 }
197}
198
199impl<const MAX_LEN: usize, C: SmallChar> Ord for SmallString<MAX_LEN, C> {
200 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
201 self.data[..self.len].cmp(&other.data[..other.len])
202 }
203}
204
205impl<const MAX_LEN: usize, C: SmallChar> std::fmt::Display for SmallString<MAX_LEN, C> {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207 write!(f, "{}", self.as_str())
208 }
209}
210
211#[cfg(test)]
212mod tests;