Skip to main content

jiff_core/util/
mod.rs

1/*!
2Small shared utilities used by Jiff.
3*/
4
5#[cfg(feature = "alloc")]
6pub(crate) mod crc32;
7
8/// A slice that is either `'static` or on the heap.
9///
10/// This is useful for representing a sequence of data that can be either
11/// created at runtime and put on the heap (when dynamic memory allocation is
12/// needed), or when it needs to be constructed at compile time. For example,
13/// this is used to represent time zone transitions inside this crate's TZif
14/// representation.
15///
16/// The downside of this type is that `T` cannot contain any borrows.
17///
18/// This is similar to `SmallStr`, but there is no array-only variant. As such,
19/// this isn't intended for small data. (Indeed, time zone transitions can get
20/// pretty long.) This also makes the API simpler: all constructors are
21/// infallible.
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct MaybeStaticSlice<T: 'static> {
24    kind: MaybeStaticSliceKind<T>,
25}
26
27#[derive(Clone, Debug, Eq, PartialEq)]
28enum MaybeStaticSliceKind<T: 'static> {
29    Static(&'static [T]),
30    #[cfg(feature = "alloc")]
31    Heap(alloc::boxed::Box<[T]>),
32}
33
34impl<T: 'static> MaybeStaticSlice<T> {
35    /// Creates a new static slice from the data provided.
36    #[inline]
37    pub const fn statik(data: &'static [T]) -> MaybeStaticSlice<T> {
38        let kind = MaybeStaticSliceKind::Static(data);
39        MaybeStaticSlice { kind }
40    }
41
42    /// Creates a new slice on the heap from the data provided.
43    #[cfg(feature = "alloc")]
44    #[inline]
45    pub const fn heap(data: alloc::boxed::Box<[T]>) -> MaybeStaticSlice<T> {
46        let kind = MaybeStaticSliceKind::Heap(data);
47        MaybeStaticSlice { kind }
48    }
49
50    /// Returns the underlying data as a slice.
51    #[inline]
52    pub const fn as_slice(&self) -> &[T] {
53        match self.kind {
54            MaybeStaticSliceKind::Static(slice) => slice,
55            #[cfg(feature = "alloc")]
56            MaybeStaticSliceKind::Heap(ref slice) => slice,
57        }
58    }
59}
60
61impl<T: 'static> From<&'static [T]> for MaybeStaticSlice<T> {
62    #[inline]
63    fn from(data: &'static [T]) -> MaybeStaticSlice<T> {
64        MaybeStaticSlice::statik(data)
65    }
66}
67
68#[cfg(feature = "alloc")]
69impl<T: 'static> From<alloc::boxed::Box<[T]>> for MaybeStaticSlice<T> {
70    #[inline]
71    fn from(data: alloc::boxed::Box<[T]>) -> MaybeStaticSlice<T> {
72        MaybeStaticSlice::heap(data)
73    }
74}
75
76impl<T: 'static> core::ops::Deref for MaybeStaticSlice<T> {
77    type Target = [T];
78
79    #[inline]
80    fn deref(&self) -> &[T] {
81        self.as_slice()
82    }
83}
84
85/// A "small" string that usually lives in an array.
86///
87/// When the string is too big, it spills over into the heap. Generally
88/// speaking, this should only be used when spilling into the heap is
89/// exceptionally rare. For example, for representing pathological user data
90/// (like very long time zone abbreviations).
91///
92/// The `ARRAY_CAPACITY_MAX` parameter defines the maximum length of a string
93/// that will fit in an array backed storage. This number cannot be any
94/// bigger than `255`.
95///
96/// In core-only environments, this always uses array backed storage or static
97/// data. Static data is only used when `SmallStr::from("some static string")`
98/// is used to construct a `SmallStr`. This is useful when constructing static
99/// data structures.
100#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)]
101pub struct SmallStr<const ARRAY_CAPACITY_MAX: usize> {
102    kind: SmallStrKind<ARRAY_CAPACITY_MAX>,
103}
104
105#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
106enum SmallStrKind<const ARRAY_CAPACITY_MAX: usize> {
107    Array(ArrayStr<ARRAY_CAPACITY_MAX>),
108    // TODO: Reconsider this variant. I'm not sure it's really necessary, and
109    // it makes the size of all `SmallStr` values bigger.
110    Static(&'static str),
111    #[cfg(feature = "alloc")]
112    Heap(alloc::boxed::Box<str>),
113}
114
115impl<const ARRAY_CAPACITY_MAX: usize> SmallStr<ARRAY_CAPACITY_MAX> {
116    /// Creates a new array-or-heap backed string depending on the length.
117    ///
118    /// In environments with dynamic memory allocation, this never returns
119    /// `None`. In core-only environments, this may return `None` when the
120    /// string length exceeds the maximum capacity.
121    #[inline]
122    pub fn new(s: &str) -> Option<SmallStr<ARRAY_CAPACITY_MAX>> {
123        SmallStr::try_array(s).or_else(|| {
124            #[cfg(not(feature = "alloc"))]
125            {
126                None
127            }
128            #[cfg(feature = "alloc")]
129            {
130                Some(SmallStr::from(alloc::boxed::Box::<str>::from(s)))
131            }
132        })
133    }
134
135    /// Like `new` but always spills to the heap when the given string does not
136    /// fit in this string's fixed capacity.
137    ///
138    /// This is only available when the `alloc` crate feature is enabled.
139    #[cfg(feature = "alloc")]
140    #[inline]
141    pub fn new_or_heap(s: &str) -> SmallStr<ARRAY_CAPACITY_MAX> {
142        SmallStr::try_array(s).unwrap_or_else(|| {
143            SmallStr::from(alloc::boxed::Box::<str>::from(s))
144        })
145    }
146
147    /// Like `SmallStr::new`, but this never returns a string on the heap.
148    ///
149    /// This is useful when you want a `SmallStr` that is guaranteed to never
150    /// be on the heap. For example, when constructing static data.
151    ///
152    /// If the string exceeds the maximum capacity, then `None` is returned.
153    #[inline]
154    pub const fn try_array(s: &str) -> Option<SmallStr<ARRAY_CAPACITY_MAX>> {
155        let Some(astr) = ArrayStr::new(s) else { return None };
156        let kind = SmallStrKind::Array(astr);
157        Some(SmallStr { kind })
158    }
159
160    /// Like `SmallStr::new`, but this never returns a string on the heap.
161    ///
162    /// This is useful when you want a `SmallStr` that is guaranteed to never
163    /// be on the heap. For example, when constructing static data.
164    ///
165    /// # Panics
166    ///
167    /// If the string exceeds the maximum capacity, then this routine panics.
168    #[inline]
169    pub const fn array(s: &str) -> SmallStr<ARRAY_CAPACITY_MAX> {
170        // MSRV(1.83): We can use `unwrap()` in a const context, so this
171        // routine isn't as necessary. But it's still nice.
172        let Some(astr) = ArrayStr::new(s) else { panic!("string too big") };
173        let kind = SmallStrKind::Array(astr);
174        SmallStr { kind }
175    }
176
177    /// Like `SmallStr::new`, but only accepts a static string.
178    ///
179    /// This never fails.
180    #[inline]
181    pub const fn statik(s: &'static str) -> SmallStr<ARRAY_CAPACITY_MAX> {
182        let kind = SmallStrKind::Static(s);
183        SmallStr { kind }
184    }
185
186    /// Returns the maximum capacity for this small string's array storage.
187    #[inline]
188    pub const fn array_capacity_max() -> usize {
189        ARRAY_CAPACITY_MAX
190    }
191
192    /// Returns this small string as a string slice.
193    #[inline]
194    pub const fn as_str(&self) -> &str {
195        match self.kind {
196            SmallStrKind::Array(ref astr) => astr.as_str(),
197            SmallStrKind::Static(s) => s,
198            #[cfg(feature = "alloc")]
199            SmallStrKind::Heap(ref s) => s,
200        }
201    }
202}
203
204/// Return a `SmallStr` that is guaranteed to live as an array.
205impl<const ARRAY_CAPACITY_MAX: usize> From<ArrayStr<ARRAY_CAPACITY_MAX>>
206    for SmallStr<ARRAY_CAPACITY_MAX>
207{
208    #[inline]
209    fn from(s: ArrayStr<ARRAY_CAPACITY_MAX>) -> SmallStr<ARRAY_CAPACITY_MAX> {
210        let kind = SmallStrKind::Array(s);
211        SmallStr { kind }
212    }
213}
214
215/// Return a `SmallStr` that is guaranteed to live as a static string.
216impl<const ARRAY_CAPACITY_MAX: usize> From<&'static str>
217    for SmallStr<ARRAY_CAPACITY_MAX>
218{
219    #[inline]
220    fn from(s: &'static str) -> SmallStr<ARRAY_CAPACITY_MAX> {
221        SmallStr::statik(s)
222    }
223}
224
225/// Return a `SmallStr` that is guaranteed to live on the heap.
226#[cfg(feature = "alloc")]
227impl<const ARRAY_CAPACITY_MAX: usize> From<alloc::boxed::Box<str>>
228    for SmallStr<ARRAY_CAPACITY_MAX>
229{
230    #[inline]
231    fn from(s: alloc::boxed::Box<str>) -> SmallStr<ARRAY_CAPACITY_MAX> {
232        let kind = SmallStrKind::Heap(s);
233        SmallStr { kind }
234    }
235}
236
237impl<const ARRAY_CAPACITY_MAX: usize> core::ops::Deref
238    for SmallStr<ARRAY_CAPACITY_MAX>
239{
240    type Target = str;
241
242    #[inline]
243    fn deref(&self) -> &str {
244        SmallStr::<ARRAY_CAPACITY_MAX>::as_str(self)
245    }
246}
247
248impl<const ARRAY_CAPACITY_MAX: usize> AsRef<str>
249    for SmallStr<ARRAY_CAPACITY_MAX>
250{
251    #[inline]
252    fn as_ref(&self) -> &str {
253        self.as_str()
254    }
255}
256
257impl<const ARRAY_CAPACITY_MAX: usize> PartialEq<str>
258    for SmallStr<ARRAY_CAPACITY_MAX>
259{
260    #[inline]
261    fn eq(&self, rhs: &str) -> bool {
262        self.as_str() == rhs
263    }
264}
265
266impl<const ARRAY_CAPACITY_MAX: usize> PartialEq<&str>
267    for SmallStr<ARRAY_CAPACITY_MAX>
268{
269    #[inline]
270    fn eq(&self, rhs: &&str) -> bool {
271        self.as_str() == *rhs
272    }
273}
274
275impl<const ARRAY_CAPACITY_MAX: usize> PartialEq<SmallStr<ARRAY_CAPACITY_MAX>>
276    for str
277{
278    #[inline]
279    fn eq(&self, rhs: &SmallStr<ARRAY_CAPACITY_MAX>) -> bool {
280        self == rhs.as_str()
281    }
282}
283
284impl<const ARRAY_CAPACITY_MAX: usize> PartialOrd<str>
285    for SmallStr<ARRAY_CAPACITY_MAX>
286{
287    #[inline]
288    fn partial_cmp(&self, rhs: &str) -> Option<core::cmp::Ordering> {
289        self.as_str().partial_cmp(rhs)
290    }
291}
292
293impl<const ARRAY_CAPACITY_MAX: usize> PartialOrd<&str>
294    for SmallStr<ARRAY_CAPACITY_MAX>
295{
296    #[inline]
297    fn partial_cmp(&self, rhs: &&str) -> Option<core::cmp::Ordering> {
298        self.as_str().partial_cmp(*rhs)
299    }
300}
301
302impl<const ARRAY_CAPACITY_MAX: usize> PartialOrd<SmallStr<ARRAY_CAPACITY_MAX>>
303    for str
304{
305    #[inline]
306    fn partial_cmp(
307        &self,
308        rhs: &SmallStr<ARRAY_CAPACITY_MAX>,
309    ) -> Option<core::cmp::Ordering> {
310        self.partial_cmp(rhs.as_str())
311    }
312}
313
314impl<const ARRAY_CAPACITY_MAX: usize> core::fmt::Debug
315    for SmallStr<ARRAY_CAPACITY_MAX>
316{
317    #[inline]
318    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
319        core::fmt::Debug::fmt(self.as_str(), f)
320    }
321}
322
323impl<const ARRAY_CAPACITY_MAX: usize> core::fmt::Display
324    for SmallStr<ARRAY_CAPACITY_MAX>
325{
326    #[inline]
327    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
328        core::fmt::Display::fmt(self.as_str(), f)
329    }
330}
331
332#[cfg(feature = "defmt")]
333impl<const ARRAY_CAPACITY_MAX: usize> defmt::Format
334    for SmallStr<ARRAY_CAPACITY_MAX>
335{
336    #[inline]
337    fn format(&self, f: defmt::Formatter) {
338        defmt::write!(f, "{=str}", self.as_str())
339    }
340}
341
342/// A simple array-backed string type with a fixed capacity.
343///
344/// This is used by Jiff in lieu of a `Box<str>` for supporting core-only
345/// environments without a dynamic memory allocator. For example, this is used
346/// to represent time zone abbreviations which can be relied upon to be short.
347///
348/// `N` must be less than `256` so that its length can be represented by an
349/// unsigned 8-bit integer.
350///
351/// An `ArrayStr` is guaranteed to be valid UTF-8.
352#[derive(Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)]
353pub struct ArrayStr<const N: usize> {
354    // If it's advantageous enough, we could use an array of uninitialized
355    // bytes. But it's not clear that it's worth doing. ---AG
356    /// The number of bytes used by the string in `bytes`.
357    ///
358    /// (We could technically save this byte in some cases and use a NUL
359    /// terminator. For example, since we don't permit NUL bytes in POSIX time
360    /// zone abbreviation strings, but this is simpler and only one byte and
361    /// generalizes. And we're not really trying to micro-optimize the storage
362    /// requirements when we use these array strings. Or at least, I don't know
363    /// of a reason to.)
364    len: u8,
365    /// The UTF-8 bytes that make up the string.
366    ///
367    /// This array---the entire array---is always valid UTF-8. And
368    /// the `0..self.len` sub-slice is also always valid UTF-8.
369    bytes: [u8; N],
370}
371
372impl<const N: usize> ArrayStr<N> {
373    /// Creates a new fixed capacity string.
374    ///
375    /// If the given string exceeds `N` bytes, then this returns
376    /// `None`.
377    #[inline]
378    pub const fn new(s: &str) -> Option<ArrayStr<N>> {
379        let len = s.len();
380        if len > N {
381            return None;
382        }
383        let mut bytes = [0; N];
384        let mut i = 0;
385        while i < s.as_bytes().len() {
386            bytes[i] = s.as_bytes()[i];
387            i += 1;
388        }
389        // OK because we don't ever use anything bigger than u8::MAX for `N`.
390        // And we probably shouldn't, because that would be a pretty chunky
391        // array. If such a thing is needed, please file an issue to discuss.
392        debug_assert!(N <= u8::MAX as usize, "size of ArrayStr is too big");
393        Some(ArrayStr { len: len as u8, bytes })
394    }
395
396    /// Returns the capacity of this array string.
397    #[inline]
398    pub const fn capacity() -> usize {
399        N
400    }
401
402    /// Append the bytes given to the end of this string.
403    ///
404    /// If the capacity would be exceeded, then this is a no-op and `false`
405    /// is returned. Otherwise, all of `s` is written to this array string and
406    /// `true` is returned.
407    #[inline]
408    pub fn push_str(&mut self, s: &str) -> bool {
409        let len = self.len as usize;
410        let Some(new_len) = len.checked_add(s.len()) else { return false };
411        if new_len > N {
412            return false;
413        }
414
415        let mut i = len;
416        while i < new_len {
417            self.bytes[i] = s.as_bytes()[i - len];
418            i += 1;
419        }
420        // OK because we don't ever use anything bigger than u8::MAX for `N`.
421        // And we probably shouldn't, because that would be a pretty chunky
422        // array. If such a thing is needed, please file an issue to discuss.
423        debug_assert!(N <= u8::MAX as usize, "size of ArrayStr is too big");
424        self.len = new_len as u8;
425        true
426    }
427
428    /// Returns this array string as a string slice.
429    #[inline]
430    pub const fn as_str(&self) -> &str {
431        // SAFETY: Firstly, the unchecked UTF-8 conversion is correct because
432        // the constructor and all mutators only accept `&str`. All mutators
433        // (just `push_str` at time of writing) only ever concatenates the
434        // given string to what is already there. Any UTF-8 string concatenated
435        // with any other UTF-8 string produces a UTF-8 string.
436        //
437        // Secondly, `self.bytes` is always valid and initialized. And
438        // `self.len` is managed as the length of bytes that make up the
439        // string.
440        unsafe {
441            core::str::from_utf8_unchecked(core::slice::from_raw_parts(
442                self.bytes.as_ptr(),
443                self.len as usize,
444            ))
445        }
446    }
447}
448
449impl<const N: usize> core::ops::Deref for ArrayStr<N> {
450    type Target = str;
451
452    #[inline]
453    fn deref(&self) -> &str {
454        ArrayStr::<N>::as_str(self)
455    }
456}
457
458impl<const N: usize> AsRef<str> for ArrayStr<N> {
459    #[inline]
460    fn as_ref(&self) -> &str {
461        self.as_str()
462    }
463}
464
465impl<const N: usize> PartialEq<str> for ArrayStr<N> {
466    #[inline]
467    fn eq(&self, rhs: &str) -> bool {
468        self.as_str() == rhs
469    }
470}
471
472impl<const N: usize> PartialEq<&str> for ArrayStr<N> {
473    #[inline]
474    fn eq(&self, rhs: &&str) -> bool {
475        self.as_str() == *rhs
476    }
477}
478
479impl<const N: usize> PartialEq<ArrayStr<N>> for str {
480    #[inline]
481    fn eq(&self, rhs: &ArrayStr<N>) -> bool {
482        self == rhs.as_str()
483    }
484}
485
486impl<const N: usize> PartialOrd<str> for ArrayStr<N> {
487    #[inline]
488    fn partial_cmp(&self, rhs: &str) -> Option<core::cmp::Ordering> {
489        self.as_str().partial_cmp(rhs)
490    }
491}
492
493impl<const N: usize> PartialOrd<&str> for ArrayStr<N> {
494    #[inline]
495    fn partial_cmp(&self, rhs: &&str) -> Option<core::cmp::Ordering> {
496        self.as_str().partial_cmp(*rhs)
497    }
498}
499
500impl<const N: usize> PartialOrd<ArrayStr<N>> for str {
501    #[inline]
502    fn partial_cmp(&self, rhs: &ArrayStr<N>) -> Option<core::cmp::Ordering> {
503        self.partial_cmp(rhs.as_str())
504    }
505}
506
507impl<const N: usize> core::fmt::Debug for ArrayStr<N> {
508    #[inline]
509    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
510        core::fmt::Debug::fmt(self.as_str(), f)
511    }
512}
513
514impl<const N: usize> core::fmt::Display for ArrayStr<N> {
515    #[inline]
516    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
517        core::fmt::Display::fmt(self.as_str(), f)
518    }
519}
520
521impl<const N: usize> core::fmt::Write for ArrayStr<N> {
522    #[inline]
523    fn write_str(&mut self, s: &str) -> core::fmt::Result {
524        if self.push_str(s) {
525            Ok(())
526        } else {
527            Err(core::fmt::Error)
528        }
529    }
530}
531
532#[cfg(feature = "defmt")]
533impl<const N: usize> defmt::Format for ArrayStr<N> {
534    #[inline]
535    fn format(&self, f: defmt::Formatter) {
536        defmt::write!(f, "{=str}", self.as_str())
537    }
538}
539
540/// Parses an `OsStr` into a `&str` when `&[u8]` isn't easily available.
541///
542/// The main difference between this and `OsStr::to_str` is that this will
543/// be a zero-cost conversion on Unix platforms to `&[u8]`. On Windows, this
544/// will do UTF-8 validation and return an error if it's invalid UTF-8.
545// MSRV(1.74): Use `OsStr::as_encoded_bytes` and delete this routine.
546#[cfg(feature = "std")]
547pub(crate) fn os_str_bytes<'o, O>(os_str: &'o O) -> Option<&'o [u8]>
548where
549    O: ?Sized + AsRef<std::ffi::OsStr>,
550{
551    let os_str = os_str.as_ref();
552    #[cfg(unix)]
553    {
554        use std::os::unix::ffi::OsStrExt;
555        Some(os_str.as_bytes())
556    }
557    #[cfg(not(unix))]
558    {
559        // It is suspect that we're doing UTF-8 validation and then throwing
560        // away the fact that we did UTF-8 validation. So this could lead
561        // to an extra UTF-8 check if the caller ultimately needs UTF-8. If
562        // that's important, we can add a new API that returns a `&str`. But it
563        // probably won't matter because an `OsStr` in this crate is usually
564        // just an environment variable.
565        os_str.to_str().map(|s| s.as_bytes())
566    }
567}
568
569#[cfg(test)]
570mod tests {
571    use core::fmt::Write;
572
573    use super::*;
574
575    #[test]
576    fn fmt_write() {
577        let mut dst = ArrayStr::<5>::new("").unwrap();
578        assert!(write!(&mut dst, "abcd").is_ok());
579        assert!(write!(&mut dst, "e").is_ok());
580        assert!(write!(&mut dst, "f").is_err());
581    }
582
583    #[test]
584    fn array_str_size() {
585        assert_eq!(7, core::mem::size_of::<ArrayStr::<6>>());
586    }
587}