const_slice/
lib.rs

1//! Compile-time constant variants of several slice types over dynamically allocated types,
2//! implemented as slices over a fixed-capacity array (controlled by const generics).
3//!
4//! The types in this crate will panic at compile-time if they would exceed the size of their array
5//! (currently produces a very unhelpful error - see [#51999](https://github.com/rust-lang/rust/issues/51999))
6#![no_std]
7use core::{
8    cmp,
9    mem::{self, MaybeUninit},
10    ops, slice, str,
11};
12
13/// A slice-like type with a fixed-capacity array as storage to be used with compile-time constants.
14///
15/// Causes an unfriendly compile-time error when the size of the slice exceeds its capacity.
16#[derive(Clone, Copy, Debug)]
17pub struct ArraySlice<const N: usize> {
18    buf: [MaybeUninit<u8>; N],
19    len: usize,
20}
21
22impl<const N: usize> ArraySlice<N> {
23    /// Creates a new [`ArraySlice`] from an array of bytes.
24    pub const fn from_array(array: [u8; N]) -> Self {
25        ArraySlice::from_bytes(&array)
26    }
27
28    /// Creates a new [`ArraySlice`] from a slice of bytes.
29    ///
30    /// # Panics
31    /// If the length of the slice exceeds the capacity of the array.
32    pub const fn from_bytes(slice: &[u8]) -> Self {
33        if slice.len() > N {
34            panic!("length of slice exceeds capacity of ArraySlice");
35        }
36        let mut buf = [MaybeUninit::uninit(); N];
37        let mut i = 0;
38        while i < slice.len() {
39            buf[i] = MaybeUninit::new(slice[i]);
40            i += 1;
41        }
42        Self {
43            buf,
44            len: slice.len(),
45        }
46    }
47
48    /// Extends the slice by value with a slice of bytes.
49    ///
50    /// # Panics
51    /// If the length of the slice exceeds the remaining space in the array.
52    pub const fn with_bytes(self, other: &[u8]) -> Self {
53        if self.len() + other.len() > N {
54            panic!("length of slice exceeds remaining space in ArraySlice");
55        }
56        let x = self.len();
57        let mut buf = *self.buf();
58        let mut i = 0;
59        while i < other.len() {
60            buf[x + i] = MaybeUninit::new(other[i]);
61            i += 1;
62        }
63        Self {
64            buf,
65            len: other.len() + self.len(),
66        }
67    }
68
69    /// Returns a reference to the internal (partially uninitialized) array.
70    #[inline(always)]
71    pub const fn buf(&self) -> &[MaybeUninit<u8>; N] {
72        &self.buf
73    }
74
75    /// Returns the number of filled bytes in the internal array.
76    #[inline(always)]
77    pub const fn len(&self) -> usize {
78        self.len
79    }
80
81    /// Returns the number of unfilled bytes in the internal array.
82    #[inline(always)]
83    pub const fn remaining(&self) -> usize {
84        N - self.len
85    }
86
87    /// Returns if the internal array is completely unfilled.
88    #[inline(always)]
89    pub const fn is_empty(&self) -> bool {
90        self.len == 0
91    }
92
93    /// Returns the slice of the internal array that is filled.
94    pub const fn as_bytes(&self) -> &[u8] {
95        // SAFETY: buf will always contain data valid for `length * size_of::<u8>()`
96        // and *const MaybeUninit<T> and *const T are identical.
97        unsafe { slice::from_raw_parts(mem::transmute(self.buf().as_ptr()), self.len()) }
98    }
99}
100
101impl<const N: usize> ops::Deref for ArraySlice<N> {
102    type Target = [u8];
103
104    fn deref(&self) -> &Self::Target {
105        self.as_bytes()
106    }
107}
108
109impl<const N: usize, const M: usize> cmp::PartialEq<ArraySlice<M>> for ArraySlice<N> {
110    fn eq(&self, other: &ArraySlice<M>) -> bool {
111        self.as_bytes() == other.as_bytes()
112    }
113}
114
115/// A string with a fixed-capacity array as storage to be used in compile-time constants.
116///
117/// Causes an unfriendly compile-time error when the size of the string exceeds its capacity.
118///
119/// This struct always upholds that it contains valid utf-8.
120#[derive(Clone, Copy, Debug, PartialEq)]
121pub struct ConstString<const N: usize>(ArraySlice<N>);
122
123impl<const N: usize> ConstString<N> {
124    /// Creates a new [`ConstString`] from a slice of bytes.
125    ///
126    /// Will return [`ConstString::ErrTooLong`] if the array is longer than the capacity of the [`ConstString`].
127    ///
128    /// # Safety
129    /// The slice must contain valid utf-8.
130    ///
131    /// # Panics
132    /// If the length of the slice exceeds the capacity of the array.
133    pub const unsafe fn from_bytes(slice: &[u8]) -> Self {
134        Self(ArraySlice::from_bytes(slice))
135    }
136
137    /// Creates a new [`ConstString`] from a `&str`.
138    ///
139    /// # Panics
140    /// If the length of the string exceeds the capacity of the array.
141    #[inline(always)]
142    pub const fn from_str(string: &str) -> Self {
143        // SAFETY: &str always contains valid utf-8.
144        unsafe { Self::from_bytes(string.as_bytes()) }
145    }
146
147    /// Extends the array in a [`ConstString`] by value with a slice of bytes.
148    ///
149    /// # Safety
150    /// The slice must contain valid utf-8.
151    ///
152    /// # Panics
153    /// If the length of the string exceeds the remaining space in the array.
154    pub const unsafe fn with_bytes(self, other: &[u8]) -> Self {
155        Self(self.0.with_bytes(other))
156    }
157
158    /// Extends the array in a [`ConstString`] by value with a string slice.
159    #[inline(always)]
160    pub const fn with_str(self, other: &str) -> Self {
161        // SAFETY: &str always contains valid utf-8.
162        unsafe { self.with_bytes(other.as_bytes()) }
163    }
164
165    /// Extends the array in a [`ConstString`] by value with another [`ConstString`].
166    #[inline(always)]
167    pub const fn with<const M: usize>(self, other: ConstString<M>) -> Self {
168        self.with_str(other.as_str())
169    }
170
171    /// Returns the string in this [`ConstString`] as an &str
172    pub const fn as_str(&self) -> &str {
173        // SAFETY: this struct is guaranteed to contain valid utf-8.
174        unsafe { str::from_utf8_unchecked(self.0.as_bytes()) }
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn it_works() {
184        const FIRST: &str = "mary had a";
185        const SECOND: &str = " little lamb.";
186
187        const BOTH: ConstString<32> = ConstString::from_str(FIRST).with_str(SECOND);
188
189        assert_eq!(BOTH, ConstString::from_str("mary had a little lamb."));
190    }
191}