askama/
ascii_str.rs

1// FIXME: Replace `AsciiChar` with `[core:ascii::Char]` once [#110998] is stable
2// [#110998]: https://github.com/rust-lang/rust/issues/110998
3
4#![allow(unreachable_pub)]
5
6use core::ops::{Deref, Index, IndexMut};
7
8pub use _ascii_char::AsciiChar;
9
10/// A string that only contains ASCII characters, same layout as [`str`].
11#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
12#[repr(transparent)]
13pub struct AsciiStr([AsciiChar]);
14
15impl AsciiStr {
16    #[inline]
17    pub const fn new_sized<const N: usize>(src: &str) -> [AsciiChar; N] {
18        if !src.is_ascii() || src.len() > N {
19            panic!();
20        }
21
22        let src = src.as_bytes();
23        let mut result = [AsciiChar::NULL; N];
24        let mut i = 0;
25        while i < src.len() {
26            result[i] = AsciiChar::new(src[i]);
27            i += 1;
28        }
29        result
30    }
31
32    #[inline]
33    pub const fn from_slice(src: &[AsciiChar]) -> &Self {
34        // SAFETY: `Self` is transparent over `[AsciiChar]`.
35        unsafe { core::mem::transmute::<&[AsciiChar], &AsciiStr>(src) }
36    }
37
38    #[inline]
39    pub const fn as_str(&self) -> &str {
40        // SAFETY: `Self` has the same layout as `str`,
41        // and all ASCII characters are valid UTF-8 characters.
42        unsafe { core::mem::transmute::<&AsciiStr, &str>(self) }
43    }
44
45    #[inline]
46    pub const fn len(&self) -> usize {
47        self.0.len()
48    }
49
50    #[inline]
51    pub const fn is_empty(&self) -> bool {
52        self.0.is_empty()
53    }
54}
55
56// Must not implement `DerefMut`. Not every `char` is an ASCII character.
57impl Deref for AsciiStr {
58    type Target = str;
59
60    #[inline]
61    fn deref(&self) -> &Self::Target {
62        self.as_str()
63    }
64}
65
66impl<Idx> Index<Idx> for AsciiStr
67where
68    [AsciiChar]: Index<Idx, Output = [AsciiChar]>,
69{
70    type Output = [AsciiChar];
71
72    #[inline]
73    fn index(&self, index: Idx) -> &Self::Output {
74        &self.0[index]
75    }
76}
77
78impl<Idx> IndexMut<Idx> for AsciiStr
79where
80    [AsciiChar]: IndexMut<Idx, Output = [AsciiChar]>,
81{
82    #[inline]
83    fn index_mut(&mut self, index: Idx) -> &mut Self::Output {
84        &mut self.0[index]
85    }
86}
87
88impl Default for &'static AsciiStr {
89    #[inline]
90    fn default() -> Self {
91        // SAFETY: `Self` has the same layout as `str`.
92        unsafe { core::mem::transmute::<&str, &AsciiStr>("") }
93    }
94}
95
96impl AsciiChar {
97    pub const NULL: AsciiChar = AsciiChar::new(0);
98
99    #[inline]
100    pub const fn slice_as_bytes<const N: usize>(src: &[AsciiChar; N]) -> &[u8; N] {
101        // SAFETY: `[AsciiChar]` has the same layout as `[u8]`.
102        unsafe { core::mem::transmute::<&[AsciiChar; N], &[u8; N]>(src) }
103    }
104
105    #[inline]
106    pub const fn two_digits(d: u32) -> [Self; 2] {
107        const ALPHABET: &[u8; 10] = b"0123456789";
108
109        if d >= ALPHABET.len().pow(2) as u32 {
110            panic!();
111        }
112        [
113            Self::new(ALPHABET[d as usize / ALPHABET.len()]),
114            Self::new(ALPHABET[d as usize % ALPHABET.len()]),
115        ]
116    }
117
118    #[inline]
119    pub const fn two_hex_digits(d: u32) -> [Self; 2] {
120        const ALPHABET: &[u8; 16] = b"0123456789abcdef";
121
122        if d >= ALPHABET.len().pow(2) as u32 {
123            panic!();
124        }
125        [
126            Self::new(ALPHABET[d as usize / ALPHABET.len()]),
127            Self::new(ALPHABET[d as usize % ALPHABET.len()]),
128        ]
129    }
130}
131
132mod _ascii_char {
133    /// A character that is known to be in ASCII range, same layout as [`u8`].
134    #[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
135    #[repr(transparent)]
136    pub struct AsciiChar(u8);
137
138    impl AsciiChar {
139        #[inline]
140        pub const fn new(c: u8) -> Self {
141            if c.is_ascii() { Self(c) } else { panic!() }
142        }
143    }
144}