const_panic/array_string.rs
1use crate::{
2 utils::{bytes_up_to, RangedBytes},
3 FmtArg, PanicFmt, PanicVal,
4};
5
6use core::{
7 cmp::PartialEq,
8 fmt::{self, Debug},
9};
10
11/// For precomputing a panic message.
12///
13#[cfg_attr(feature = "docsrs", doc(cfg(feature = "non_basic")))]
14#[derive(Copy, Clone)]
15pub struct ArrayString<const CAP: usize> {
16 pub(crate) len: u32,
17 pub(crate) buffer: [u8; CAP],
18}
19
20/// Equivalent of `ArrayString` which can only be up to 255 bytes long.
21///
22/// This stores the length as a `u8`, while `ArrayString` stores it as a `u32`,
23/// making this 3 bytes smaller and 1-aligned (while ArrayString is aligned to a `u32`).
24#[derive(Copy, Clone)]
25pub(crate) struct TinyString<const CAP: usize> {
26 len: u8,
27 buffer: [u8; CAP],
28}
29
30const fn add_up_lengths(mut strings: &[&str]) -> usize {
31 let mut len = 0;
32 while let [x, rem @ ..] = strings {
33 len += x.len();
34 strings = rem;
35 }
36 len
37}
38
39impl<const CAP: usize> ArrayString<CAP> {
40 /// Constructs an `ArrayString` from a `&str`
41 ///
42 /// # Panics
43 ///
44 /// Panics if `string` is larger than `CAP`.
45 ///
46 /// # Example
47 ///
48 /// ```rust
49 /// use const_panic::ArrayString;
50 ///
51 /// assert_eq!(ArrayString::<16>::new("Hello, world!"), "Hello, world!");
52 /// ```
53 pub const fn new(string: &str) -> Self {
54 Self::concat(&[string])
55 }
56
57 /// Constructs an `ArrayString` by concatenating zero or more `&str`s
58 ///
59 /// # Panics
60 ///
61 /// Panics if the concatenated string would be longer than `CAP`.
62 ///
63 /// # Example
64 ///
65 /// ```rust
66 /// use const_panic::ArrayString;
67 ///
68 /// assert_eq!(
69 /// ArrayString::<99>::concat(&["This ", "is ", "a string"]),
70 /// "This is a string"
71 /// );
72 /// ```
73 pub const fn concat(strings: &[&str]) -> Self {
74 let mut len = 0u32;
75 let mut buffer = [0u8; CAP];
76
77 let mut mstrings = strings;
78 while let [string, ref rem @ ..] = *mstrings {
79 mstrings = rem;
80 let mut bytes = string.as_bytes();
81 while let [x, ref rem_inner @ ..] = *bytes {
82 if len == u32::MAX || len as usize >= CAP {
83 crate::concat_panic(&[&[
84 PanicVal::write_str("The input strings were longer than "),
85 PanicVal::from_usize(CAP, FmtArg::DISPLAY),
86 PanicVal::write_str(", concatenated length: "),
87 PanicVal::from_usize(add_up_lengths(strings), FmtArg::DISPLAY),
88 PanicVal::write_str(", strings: "),
89 PanicVal::from_slice_str(strings, FmtArg::DEBUG),
90 ]])
91 }
92
93 bytes = rem_inner;
94 buffer[len as usize] = x;
95 len += 1;
96 }
97 }
98
99 Self { len, buffer }
100 }
101
102 /// Constructs this string from a `&[&[PanicVal<'_>]]`.
103 ///
104 /// Returns `None` if the formatted args would be larger than `CAP`.
105 ///
106 /// # Example
107 ///
108 /// ```rust
109 /// use const_panic::{ArrayString, FmtArg, flatten_panicvals};
110 ///
111 /// assert_eq!(
112 /// ArrayString::<17>::concat_panicvals(&[
113 /// &flatten_panicvals!(FmtArg::DEBUG; 1u8, ("hello")),
114 /// &flatten_panicvals!(FmtArg::DEBUG; &[3u8, 5, 8]),
115 /// ]).unwrap(),
116 /// "1\"hello\"[3, 5, 8]",
117 /// );
118 ///
119 /// assert!(
120 /// ArrayString::<16>::concat_panicvals(&[
121 /// &flatten_panicvals!(FmtArg::DEBUG; 1u8, ("hello")),
122 /// &flatten_panicvals!(FmtArg::DEBUG; &[3u8, 5, 8]),
123 /// ]).is_none(),
124 /// );
125 ///
126 /// ```
127 ///
128 pub const fn concat_panicvals(args: &[&[PanicVal<'_>]]) -> Option<Self> {
129 match crate::concat_panic_::make_panic_string::<CAP>(args) {
130 Ok(x) => Some(x),
131 Err(_) => None,
132 }
133 }
134
135 /// Constructs this string from a `&[PanicVal<'_>]`.
136 ///
137 /// Returns `None` if the formatted args would be larger than `CAP`.
138 ///
139 /// # Example
140 ///
141 /// ```rust
142 /// use const_panic::{ArrayString, FmtArg, flatten_panicvals};
143 ///
144 /// assert_eq!(
145 /// ArrayString::<8>::from_panicvals(
146 /// &flatten_panicvals!(FmtArg::DEBUG; 100u8, "hello")
147 /// ).unwrap(),
148 /// "100hello",
149 /// );
150 ///
151 /// // trying to format panicvals into too small an ArrayString
152 /// assert!(
153 /// ArrayString::<7>::from_panicvals(
154 /// &flatten_panicvals!(FmtArg::DEBUG; 100u8, "hello")
155 /// ).is_none(),
156 /// );
157 ///
158 /// ```
159 pub const fn from_panicvals(args: &[PanicVal<'_>]) -> Option<Self> {
160 Self::concat_panicvals(&[args])
161 }
162
163 /// How long the string is in bytes.
164 ///
165 /// # Example
166 ///
167 /// ```rust
168 /// use const_panic::ArrayString;
169 ///
170 /// assert_eq!(ArrayString::<16>::new("foo").len(), 3);
171 /// assert_eq!(ArrayString::<16>::new("foo bar").len(), 7);
172 /// assert_eq!(ArrayString::<16>::new("Hello, world!").len(), 13);
173 /// ```
174 pub const fn len(&self) -> usize {
175 self.len as usize
176 }
177
178 /// Queries whether the string is empty.
179 ///
180 /// # Example
181 ///
182 /// ```rust
183 /// use const_panic::ArrayString;
184 ///
185 /// assert_eq!(ArrayString::<16>::new("").is_empty(), true);
186 /// assert_eq!(ArrayString::<16>::new("foo").is_empty(), false);
187 /// assert_eq!(ArrayString::<16>::new("foo bar").is_empty(), false);
188 /// assert_eq!(ArrayString::<16>::new("Hello, world!").is_empty(), false);
189 /// ```
190 pub const fn is_empty(&self) -> bool {
191 self.len == 0
192 }
193
194 /// Accesses the string as a byte slice.
195 ///
196 /// # Performance
197 ///
198 /// When the "rust_1_64" feature is disabled,
199 /// this takes a linear amount of time to run, proportional to `CAP - self.len()`.
200 ///
201 /// When the "rust_1_64" feature is enabled,
202 /// this takes a constant amount of time to run.
203 ///
204 /// # Example
205 ///
206 /// ```rust
207 /// use const_panic::ArrayString;
208 ///
209 /// assert_eq!(ArrayString::<16>::new("foo").as_bytes(), b"foo");
210 /// assert_eq!(ArrayString::<16>::new("foo bar").as_bytes(), b"foo bar");
211 /// assert_eq!(ArrayString::<16>::new("Hello, world!").as_bytes(), b"Hello, world!");
212 /// ```
213 pub const fn as_bytes(&self) -> &[u8] {
214 bytes_up_to(&self.buffer, self.len())
215 }
216
217 /// Gets the string.
218 ///
219 /// # Performance
220 ///
221 /// This takes a linear amount of time to run.
222 ///
223 /// # Example
224 ///
225 /// ```rust
226 /// use const_panic::ArrayString;
227 ///
228 /// assert_eq!(ArrayString::<16>::new("foo").to_str(), "foo");
229 /// assert_eq!(ArrayString::<16>::new("foo bar").to_str(), "foo bar");
230 /// assert_eq!(ArrayString::<16>::new("Hello, world!").to_str(), "Hello, world!");
231 /// ```
232 pub const fn to_str(&self) -> &str {
233 #[cfg(not(feature = "rust_1_64"))]
234 {
235 // safety: make_panic_string delegates formatting to the `write_to_buffer` macro,
236 // which is tested as producing valid utf8.
237 unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
238 }
239
240 #[cfg(feature = "rust_1_64")]
241 match core::str::from_utf8(self.as_bytes()) {
242 Ok(x) => x,
243 Err(_) => panic!("INTERNAL BUG: the string isn't valid utf-8"),
244 }
245 }
246
247 /// Creates a single element `PanicVal` borrowing from this string.
248 pub const fn to_panicvals(&self, f: FmtArg) -> [PanicVal<'_>; 1] {
249 [PanicVal::from_str(self.to_str(), f)]
250 }
251
252 /// Creates a `PanicVal` borrowing from this string.
253 pub const fn to_panicval(&self, f: FmtArg) -> PanicVal<'_> {
254 PanicVal::from_str(self.to_str(), f)
255 }
256}
257
258impl<const CAP: usize> ArrayString<CAP> {
259 pub(crate) const fn to_compact(self) -> TinyString<CAP> {
260 if self.len() <= 255 {
261 TinyString {
262 len: self.len as u8,
263 buffer: self.buffer,
264 }
265 } else {
266 crate::concat_panic(&[&[
267 PanicVal::write_str(
268 "The input string is too long, expected `length <= 255`, found length: ",
269 ),
270 PanicVal::from_usize(self.len(), FmtArg::DISPLAY),
271 ]])
272 }
273 }
274}
275
276impl<const CAP: usize> Debug for ArrayString<CAP> {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 Debug::fmt(self.to_str(), f)
279 }
280}
281
282impl<const CAP: usize> PartialEq<str> for ArrayString<CAP> {
283 fn eq(&self, str: &str) -> bool {
284 self.to_str() == str
285 }
286}
287impl<const CAP: usize> PartialEq<&str> for ArrayString<CAP> {
288 fn eq(&self, str: &&str) -> bool {
289 self == *str
290 }
291}
292
293impl<const CAP: usize> PanicFmt for ArrayString<CAP> {
294 type This = Self;
295 type Kind = crate::fmt::IsCustomType;
296 const PV_COUNT: usize = 1;
297}
298
299////////////////////////////////////////////////////////////////////////////////
300
301impl<const CAP: usize> TinyString<CAP> {
302 pub(crate) const fn ranged(&self) -> RangedBytes<&[u8]> {
303 RangedBytes {
304 start: 0,
305 end: self.len as usize,
306 bytes: &self.buffer,
307 }
308 }
309}