const-slice 0.1.0

working with slices in compile-time constants
Documentation
//! Compile-time constant variants of several slice types over dynamically allocated types,
//! implemented as slices over a fixed-capacity array (controlled by const generics).
//!
//! The types in this crate will panic at compile-time if they would exceed the size of their array
//! (currently produces a very unhelpful error - see [#51999](https://github.com/rust-lang/rust/issues/51999))
#![no_std]
use core::{
    cmp,
    mem::{self, MaybeUninit},
    ops, slice, str,
};

/// A slice-like type with a fixed-capacity array as storage to be used with compile-time constants.
///
/// Causes an unfriendly compile-time error when the size of the slice exceeds its capacity.
#[derive(Clone, Copy, Debug)]
pub struct ArraySlice<const N: usize> {
    buf: [MaybeUninit<u8>; N],
    len: usize,
}

impl<const N: usize> ArraySlice<N> {
    /// Creates a new [`ArraySlice`] from an array of bytes.
    pub const fn from_array(array: [u8; N]) -> Self {
        ArraySlice::from_bytes(&array)
    }

    /// Creates a new [`ArraySlice`] from a slice of bytes.
    ///
    /// # Panics
    /// If the length of the slice exceeds the capacity of the array.
    pub const fn from_bytes(slice: &[u8]) -> Self {
        if slice.len() > N {
            panic!("length of slice exceeds capacity of ArraySlice");
        }
        let mut buf = [MaybeUninit::uninit(); N];
        let mut i = 0;
        while i < slice.len() {
            buf[i] = MaybeUninit::new(slice[i]);
            i += 1;
        }
        Self {
            buf,
            len: slice.len(),
        }
    }

    /// Extends the slice by value with a slice of bytes.
    ///
    /// # Panics
    /// If the length of the slice exceeds the remaining space in the array.
    pub const fn with_bytes(self, other: &[u8]) -> Self {
        if self.len() + other.len() > N {
            panic!("length of slice exceeds remaining space in ArraySlice");
        }
        let x = self.len();
        let mut buf = *self.buf();
        let mut i = 0;
        while i < other.len() {
            buf[x + i] = MaybeUninit::new(other[i]);
            i += 1;
        }
        Self {
            buf,
            len: other.len() + self.len(),
        }
    }

    /// Returns a reference to the internal (partially uninitialized) array.
    #[inline(always)]
    pub const fn buf(&self) -> &[MaybeUninit<u8>; N] {
        &self.buf
    }

    /// Returns the number of filled bytes in the internal array.
    #[inline(always)]
    pub const fn len(&self) -> usize {
        self.len
    }

    /// Returns the number of unfilled bytes in the internal array.
    #[inline(always)]
    pub const fn remaining(&self) -> usize {
        N - self.len
    }

    /// Returns if the internal array is completely unfilled.
    #[inline(always)]
    pub const fn is_empty(&self) -> bool {
        self.len == 0
    }

    /// Returns the slice of the internal array that is filled.
    pub const fn as_bytes(&self) -> &[u8] {
        // SAFETY: buf will always contain data valid for `length * size_of::<u8>()`
        // and *const MaybeUninit<T> and *const T are identical.
        unsafe { slice::from_raw_parts(mem::transmute(self.buf().as_ptr()), self.len()) }
    }
}

impl<const N: usize> ops::Deref for ArraySlice<N> {
    type Target = [u8];

    fn deref(&self) -> &Self::Target {
        self.as_bytes()
    }
}

impl<const N: usize, const M: usize> cmp::PartialEq<ArraySlice<M>> for ArraySlice<N> {
    fn eq(&self, other: &ArraySlice<M>) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}

/// A string with a fixed-capacity array as storage to be used in compile-time constants.
///
/// Causes an unfriendly compile-time error when the size of the string exceeds its capacity.
///
/// This struct always upholds that it contains valid utf-8.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ConstString<const N: usize>(ArraySlice<N>);

impl<const N: usize> ConstString<N> {
    /// Creates a new [`ConstString`] from a slice of bytes.
    ///
    /// Will return [`ConstString::ErrTooLong`] if the array is longer than the capacity of the [`ConstString`].
    ///
    /// # Safety
    /// The slice must contain valid utf-8.
    ///
    /// # Panics
    /// If the length of the slice exceeds the capacity of the array.
    pub const unsafe fn from_bytes(slice: &[u8]) -> Self {
        Self(ArraySlice::from_bytes(slice))
    }

    /// Creates a new [`ConstString`] from a `&str`.
    ///
    /// # Panics
    /// If the length of the string exceeds the capacity of the array.
    #[inline(always)]
    pub const fn from_str(string: &str) -> Self {
        // SAFETY: &str always contains valid utf-8.
        unsafe { Self::from_bytes(string.as_bytes()) }
    }

    /// Extends the array in a [`ConstString`] by value with a slice of bytes.
    ///
    /// # Safety
    /// The slice must contain valid utf-8.
    ///
    /// # Panics
    /// If the length of the string exceeds the remaining space in the array.
    pub const unsafe fn with_bytes(self, other: &[u8]) -> Self {
        Self(self.0.with_bytes(other))
    }

    /// Extends the array in a [`ConstString`] by value with a string slice.
    #[inline(always)]
    pub const fn with_str(self, other: &str) -> Self {
        // SAFETY: &str always contains valid utf-8.
        unsafe { self.with_bytes(other.as_bytes()) }
    }

    /// Extends the array in a [`ConstString`] by value with another [`ConstString`].
    #[inline(always)]
    pub const fn with<const M: usize>(self, other: ConstString<M>) -> Self {
        self.with_str(other.as_str())
    }

    /// Returns the string in this [`ConstString`] as an &str
    pub const fn as_str(&self) -> &str {
        // SAFETY: this struct is guaranteed to contain valid utf-8.
        unsafe { str::from_utf8_unchecked(self.0.as_bytes()) }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        const FIRST: &str = "mary had a";
        const SECOND: &str = " little lamb.";

        const BOTH: ConstString<32> = ConstString::from_str(FIRST).with_str(SECOND);

        assert_eq!(BOTH, ConstString::from_str("mary had a little lamb."));
    }
}