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}