Skip to main content

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}