1#![no_std]
4#![deny(missing_docs)]
5
6#[cfg(any(feature = "alloc", test))]
7extern crate alloc;
8
9use core::fmt;
10use core::marker::PhantomData;
11use core::ops::Range;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct CapacityError;
16
17impl fmt::Display for CapacityError {
18 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19 f.write_str("fixed-capacity buffer is full")
20 }
21}
22
23impl core::error::Error for CapacityError {}
24
25#[derive(Clone, Copy, PartialEq, Eq)]
27pub struct FixedBytes<const N: usize> {
28 data: [u8; N],
29 len: usize,
30}
31
32impl<const N: usize> FixedBytes<N> {
33 pub const fn new() -> Self {
35 Self {
36 data: [0; N],
37 len: 0,
38 }
39 }
40
41 pub const fn empty() -> Self {
43 Self::new()
44 }
45
46 pub const fn with_size(size: usize) -> Self {
51 assert!(size <= N);
52 Self {
53 data: [0; N],
54 len: size,
55 }
56 }
57
58 pub const fn len(&self) -> usize {
60 self.len
61 }
62
63 pub const fn is_empty(&self) -> bool {
65 self.len == 0
66 }
67
68 pub const fn capacity(&self) -> usize {
70 N
71 }
72
73 pub const fn remaining_capacity(&self) -> usize {
75 N - self.len
76 }
77
78 pub fn as_bytes(&self) -> &[u8] {
80 &self.data[..self.len]
81 }
82
83 pub fn as_bytes_mut(&mut self) -> &mut [u8] {
85 &mut self.data[..self.len]
86 }
87
88 pub fn try_as_str(&self) -> Result<&str, core::str::Utf8Error> {
90 core::str::from_utf8(self.as_bytes())
91 }
92
93 pub fn as_str(&self) -> &str {
98 self.try_as_str()
99 .expect("FixedBytes contains invalid UTF-8")
100 }
101
102 pub fn clear(&mut self) {
104 self.len = 0;
105 }
106
107 pub fn truncate(&mut self, new_len: usize) {
112 assert!(new_len <= self.len);
113 self.len = new_len;
114 }
115
116 pub fn allocate(&mut self, bytes: usize) {
121 assert!(bytes <= self.remaining_capacity());
122 self.len += bytes;
123 }
124
125 pub fn try_from_slice(value: &[u8]) -> Result<Self, CapacityError> {
127 let mut result = Self::new();
128 result.try_push_slice(value)?;
129 Ok(result)
130 }
131
132 pub fn try_push_slice(&mut self, value: &[u8]) -> Result<Range<usize>, CapacityError> {
134 if value.len() > self.remaining_capacity() {
135 return Err(CapacityError);
136 }
137 let start = self.len;
138 self.len += value.len();
139 self.data[start..self.len].copy_from_slice(value);
140 Ok(start..self.len)
141 }
142
143 pub fn push_slice(&mut self, value: &[u8]) -> Range<usize> {
148 self.try_push_slice(value)
149 .expect("FixedBytes capacity exceeded")
150 }
151
152 pub fn try_push_byte(&mut self, value: u8) -> Result<usize, CapacityError> {
154 if self.len == N {
155 return Err(CapacityError);
156 }
157 let index = self.len;
158 self.data[index] = value;
159 self.len += 1;
160 Ok(index)
161 }
162
163 pub fn push_byte(&mut self, value: u8) -> usize {
168 self.try_push_byte(value)
169 .expect("FixedBytes capacity exceeded")
170 }
171}
172
173impl<const N: usize> Default for FixedBytes<N> {
174 fn default() -> Self {
175 Self::new()
176 }
177}
178
179impl<const N: usize> From<&[u8]> for FixedBytes<N> {
180 fn from(value: &[u8]) -> Self {
181 Self::try_from_slice(value).expect("FixedBytes capacity exceeded")
182 }
183}
184
185impl<const N: usize> From<&[u8; N]> for FixedBytes<N> {
186 fn from(value: &[u8; N]) -> Self {
187 Self {
188 data: *value,
189 len: N,
190 }
191 }
192}
193
194impl<const N: usize> fmt::Debug for FixedBytes<N> {
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 f.debug_tuple("FixedBytes").field(&self.as_bytes()).finish()
197 }
198}
199
200impl<const N: usize> fmt::Display for FixedBytes<N> {
201 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202 match self.try_as_str() {
203 Ok(value) => f.write_str(value),
204 Err(_) => write!(f, "{:?}", self.as_bytes()),
205 }
206 }
207}
208
209#[derive(Clone, Copy, PartialEq, Eq)]
211pub struct FixedStr<const N: usize>(FixedBytes<N>);
212
213impl<const N: usize> FixedStr<N> {
214 pub const fn new() -> Self {
216 Self(FixedBytes::new())
217 }
218
219 pub const fn len(&self) -> usize {
221 self.0.len()
222 }
223
224 pub const fn is_empty(&self) -> bool {
226 self.0.is_empty()
227 }
228
229 pub const fn capacity(&self) -> usize {
231 N
232 }
233
234 pub const fn remaining_capacity(&self) -> usize {
236 self.0.remaining_capacity()
237 }
238
239 pub fn as_str(&self) -> &str {
241 unsafe { core::str::from_utf8_unchecked(self.0.as_bytes()) }
243 }
244
245 pub fn as_bytes(&self) -> &[u8] {
247 self.0.as_bytes()
248 }
249
250 pub fn clear(&mut self) {
252 self.0.clear();
253 }
254
255 pub fn try_push_str(&mut self, value: &str) -> Result<Range<usize>, CapacityError> {
257 self.0.try_push_slice(value.as_bytes())
258 }
259
260 pub fn push_str(&mut self, value: &str) -> Range<usize> {
265 self.try_push_str(value)
266 .expect("FixedStr capacity exceeded")
267 }
268
269 pub fn try_push(&mut self, value: char) -> Result<Range<usize>, CapacityError> {
271 let mut bytes = [0; 4];
272 self.try_push_str(value.encode_utf8(&mut bytes))
273 }
274
275 pub fn truncate(&mut self, new_len: usize) {
280 assert!(self.as_str().is_char_boundary(new_len));
281 self.0.truncate(new_len);
282 }
283
284 pub const fn into_bytes(self) -> FixedBytes<N> {
286 self.0
287 }
288}
289
290impl<const N: usize> Default for FixedStr<N> {
291 fn default() -> Self {
292 Self::new()
293 }
294}
295
296impl<const N: usize> TryFrom<&str> for FixedStr<N> {
297 type Error = CapacityError;
298
299 fn try_from(value: &str) -> Result<Self, Self::Error> {
300 Ok(Self(FixedBytes::try_from_slice(value.as_bytes())?))
301 }
302}
303
304impl<const N: usize> TryFrom<FixedBytes<N>> for FixedStr<N> {
305 type Error = core::str::Utf8Error;
306
307 fn try_from(value: FixedBytes<N>) -> Result<Self, Self::Error> {
308 value.try_as_str()?;
309 Ok(Self(value))
310 }
311}
312
313impl<const N: usize> fmt::Debug for FixedStr<N> {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 f.debug_tuple("FixedStr").field(&self.as_str()).finish()
316 }
317}
318
319impl<const N: usize> fmt::Display for FixedStr<N> {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 f.write_str(self.as_str())
322 }
323}
324
325pub trait Utf16ByteOrder: Copy {
327 fn read(bytes: [u8; 2]) -> u16;
329 fn write(value: u16) -> [u8; 2];
331}
332
333#[derive(Debug, Clone, Copy, PartialEq, Eq)]
335pub struct LittleEndian;
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq)]
339pub struct BigEndian;
340
341impl Utf16ByteOrder for LittleEndian {
342 fn read(bytes: [u8; 2]) -> u16 {
343 u16::from_le_bytes(bytes)
344 }
345 fn write(value: u16) -> [u8; 2] {
346 value.to_le_bytes()
347 }
348}
349
350impl Utf16ByteOrder for BigEndian {
351 fn read(bytes: [u8; 2]) -> u16 {
352 u16::from_be_bytes(bytes)
353 }
354 fn write(value: u16) -> [u8; 2] {
355 value.to_be_bytes()
356 }
357}
358
359#[repr(C)]
361#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362pub struct FixedUtf16<const N: usize, E: Utf16ByteOrder> {
363 data: [[u8; 2]; N],
364 byte_order: PhantomData<E>,
365}
366
367pub type FixedUtf16Le<const N: usize> = FixedUtf16<N, LittleEndian>;
369pub type FixedUtf16Be<const N: usize> = FixedUtf16<N, BigEndian>;
371
372impl<const N: usize, E: Utf16ByteOrder> FixedUtf16<N, E> {
373 pub const fn new() -> Self {
375 Self {
376 data: [[0; 2]; N],
377 byte_order: PhantomData,
378 }
379 }
380
381 pub fn try_from_str(value: &str) -> Result<Self, CapacityError> {
383 let mut result = Self::new();
384 for (index, unit) in value.encode_utf16().enumerate() {
385 if index == N {
386 return Err(CapacityError);
387 }
388 result.data[index] = E::write(unit);
389 }
390 Ok(result)
391 }
392
393 pub fn as_bytes(&self) -> &[[u8; 2]; N] {
395 &self.data
396 }
397
398 pub fn decode(&self) -> impl Iterator<Item = Result<char, core::char::DecodeUtf16Error>> + '_ {
400 char::decode_utf16(
401 self.data
402 .iter()
403 .map(|bytes| E::read(*bytes))
404 .take_while(|unit| *unit != 0),
405 )
406 }
407
408 #[cfg(feature = "alloc")]
409 pub fn to_string(&self) -> Result<alloc::string::String, core::char::DecodeUtf16Error> {
411 self.decode().collect()
412 }
413}
414
415impl<const N: usize, E: Utf16ByteOrder> Default for FixedUtf16<N, E> {
416 fn default() -> Self {
417 Self::new()
418 }
419}
420
421#[cfg(feature = "bytemuck")]
422unsafe impl<const N: usize, E: Utf16ByteOrder + 'static> bytemuck::Zeroable for FixedUtf16<N, E> {}
423#[cfg(feature = "bytemuck")]
424unsafe impl<const N: usize, E: Utf16ByteOrder + 'static> bytemuck::Pod for FixedUtf16<N, E> {}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 #[test]
431 fn bytes_allow_non_utf8() {
432 let bytes = FixedBytes::<2>::try_from_slice([0xff, 0].as_slice()).unwrap();
433 assert!(bytes.try_as_str().is_err());
434 }
435
436 #[test]
437 fn fixed_str_preserves_utf8() {
438 let mut text = FixedStr::<8>::try_from("é").unwrap();
439 text.try_push('!').unwrap();
440 assert_eq!(text.as_str(), "é!");
441 }
442
443 #[test]
444 fn utf16_round_trips_both_orders() {
445 let le = FixedUtf16Le::<8>::try_from_str("A😀").unwrap();
446 let be = FixedUtf16Be::<8>::try_from_str("A😀").unwrap();
447 assert_eq!(le.to_string().unwrap(), "A😀");
448 assert_eq!(be.to_string().unwrap(), "A😀");
449 }
450}