stackstring 0.1.8

A fixed-size string
Documentation
use core::{
    cmp::{Eq, Ord, PartialEq, PartialOrd},
    convert::TryFrom,
    fmt,
    hash::Hash,
    ops::{self, Deref, DerefMut, Index, IndexMut},
    str,
};

use self::builder::StringBuilder;
use crate::error::Error;

pub mod builder;
#[cfg(feature = "rkyv-derive")]
pub mod rkyv;
#[cfg(feature = "serde-derive")]
pub mod serde;

#[derive(Copy, Clone, Eq, PartialOrd, Ord, Hash)]
pub struct String<const L: usize>(pub(crate) [u8; L]);

/// A non-zero sized inlined `String`. It doesn't keep any length, the assumption is
/// that the capacity is always utilised.
impl<const L: usize> String<L> {
    pub const fn empty() -> Self {
        Self([b' '; L])
    }

    /// Constructs a `String<L>` from an _at most_ `L` bytes-long string slice,
    /// left-padding if the slice is less than `L` bytes-long.
    ///
    /// # Example
    ///
    /// ```
    /// # use stackstring::String;
    /// let s = "three";
    /// let string = String::<9>::try_from_str_padded(s).unwrap();
    ///
    /// assert_eq!(string, "three    ");
    ///
    /// let string_err = String::<3>::try_from_str_padded(s);
    ///
    /// assert!(string_err.is_err());
    /// ```
    pub fn try_from_str_padded(s: impl AsRef<str>) -> Result<Self, Error<L>> {
        Self::try_from_bytes_padded(s.as_ref().as_bytes())
    }

    /// Constructs a `String<L>` from _at most_ `L` bytes.
    /// left-padding if the number of bytes is less than `L`.
    ///
    /// # Example
    ///
    /// ```
    /// # use stackstring::String;
    /// let bytes = b"three";
    /// let string = String::<9>::try_from_bytes_padded(bytes).unwrap();
    ///
    /// assert_eq!(string.as_bytes(), b"three    ");
    /// ```
    pub fn try_from_bytes_padded(bytes: impl AsRef<[u8]>) -> Result<Self, Error<L>> {
        let bytes = bytes.as_ref();

        if bytes.len() > L {
            return Err(Error(bytes.len()));
        }

        let mut builder = Self::builder();
        unsafe {
            builder.push_bytes_unchecked(bytes);
        }

        Ok(builder.build())
    }

    pub const fn builder() -> StringBuilder<L> {
        StringBuilder::empty()
    }

    pub fn as_str(&self) -> &str {
        &self
    }

    pub fn as_bytes(&self) -> &[u8; L] {
        &self.0
    }

    pub fn as_slice(&self) -> &[u8] {
        self.as_str().as_bytes()
    }

    /// Turns the `String` into its underlying bytes.
    pub fn into_bytes(self) -> [u8; L] {
        self.0
    }

    /// Returns whether all bytes in the `String` are empty.
    pub fn is_empty(&self) -> bool {
        self.0.iter().all(|&x| x == b' ')
    }

    /// Returns whether the first byte of the `String` is empty.
    /// Can be used as a faster version of `is_empty`.
    pub fn starts_empty(&self) -> bool {
        self.0[0] == b' '
    }
}

impl<const L: usize> Default for String<L> {
    fn default() -> Self {
        Self::empty()
    }
}

impl<const L: usize> fmt::Debug for String<L> {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&**self, f)
    }
}

impl<const L: usize> Deref for String<L> {
    type Target = str;

    fn deref(&self) -> &str {
        unsafe { core::str::from_utf8_unchecked(&self.0) }
    }
}

impl<const L: usize> DerefMut for String<L> {
    #[inline]
    fn deref_mut(&mut self) -> &mut str {
        unsafe { str::from_utf8_unchecked_mut(&mut self.0) }
    }
}

impl<const L: usize> From<[u8; L]> for String<L> {
    fn from(buf: [u8; L]) -> Self {
        String(buf)
    }
}

impl<const L: usize> TryFrom<&str> for String<L> {
    type Error = crate::error::Error<L>;

    fn try_from(s: &str) -> Result<Self, Self::Error> {
        if s.len() != L {
            return Err(s.len().into());
        }

        let mut res = Self::empty();

        res.0.copy_from_slice(s.as_bytes());

        Ok(res)
    }
}

impl<const L: usize> PartialEq for String<L> {
    #[inline]
    fn eq(&self, other: &String<L>) -> bool {
        PartialEq::eq(&self[..], &other[..])
    }

    #[inline]
    fn ne(&self, other: &String<L>) -> bool {
        PartialEq::ne(&self[..], &other[..])
    }
}

// == str eq ==
impl<const L: usize> PartialEq<&str> for String<L> {
    #[inline]
    fn eq(&self, other: &&str) -> bool {
        PartialEq::eq(&self[..], &other[..])
    }

    #[inline]
    fn ne(&self, other: &&str) -> bool {
        PartialEq::ne(&self[..], &other[..])
    }
}

impl<const L: usize> PartialEq<String<L>> for &str {
    #[inline]
    fn eq(&self, other: &String<L>) -> bool {
        PartialEq::eq(&self[..], &other[..])
    }

    #[inline]
    fn ne(&self, other: &String<L>) -> bool {
        PartialEq::ne(&self[..], &other[..])
    }
}

// == std String eq ==
impl<const L: usize> PartialEq<std::string::String> for String<L> {
    #[inline]
    fn eq(&self, other: &std::string::String) -> bool {
        PartialEq::eq(&self[..], &other[..])
    }

    #[inline]
    fn ne(&self, other: &std::string::String) -> bool {
        PartialEq::ne(&self[..], &other[..])
    }
}

impl<const L: usize> PartialEq<String<L>> for std::string::String {
    #[inline]
    fn eq(&self, other: &String<L>) -> bool {
        PartialEq::eq(&self[..], &other[..])
    }

    #[inline]
    fn ne(&self, other: &String<L>) -> bool {
        PartialEq::ne(&self[..], &other[..])
    }
}

impl<const L: usize> ops::Index<ops::Range<usize>> for String<L> {
    type Output = str;

    #[inline]
    fn index(&self, index: ops::Range<usize>) -> &str {
        &self[..][index]
    }
}

impl<const L: usize> ops::Index<ops::RangeTo<usize>> for String<L> {
    type Output = str;

    #[inline]
    fn index(&self, index: ops::RangeTo<usize>) -> &str {
        &self[..][index]
    }
}

impl<const L: usize> ops::Index<ops::RangeFrom<usize>> for String<L> {
    type Output = str;

    #[inline]
    fn index(&self, index: ops::RangeFrom<usize>) -> &str {
        &self[..][index]
    }
}

impl<const L: usize> ops::Index<ops::RangeFull> for String<L> {
    type Output = str;

    #[inline]
    fn index(&self, _index: ops::RangeFull) -> &str {
        unsafe { str::from_utf8_unchecked(&self.0) }
    }
}

impl<const L: usize> ops::Index<ops::RangeInclusive<usize>> for String<L> {
    type Output = str;

    #[inline]
    fn index(&self, index: ops::RangeInclusive<usize>) -> &str {
        Index::index(&**self, index)
    }
}

impl<const L: usize> ops::Index<ops::RangeToInclusive<usize>> for String<L> {
    type Output = str;

    #[inline]
    fn index(&self, index: ops::RangeToInclusive<usize>) -> &str {
        Index::index(&**self, index)
    }
}

impl<const L: usize> ops::IndexMut<ops::Range<usize>> for String<L> {
    #[inline]
    fn index_mut(&mut self, index: ops::Range<usize>) -> &mut str {
        &mut self[..][index]
    }
}

impl<const L: usize> ops::IndexMut<ops::RangeTo<usize>> for String<L> {
    #[inline]
    fn index_mut(&mut self, index: ops::RangeTo<usize>) -> &mut str {
        &mut self[..][index]
    }
}

impl<const L: usize> ops::IndexMut<ops::RangeFrom<usize>> for String<L> {
    #[inline]
    fn index_mut(&mut self, index: ops::RangeFrom<usize>) -> &mut str {
        &mut self[..][index]
    }
}

impl<const L: usize> ops::IndexMut<ops::RangeFull> for String<L> {
    #[inline]
    fn index_mut(&mut self, _index: ops::RangeFull) -> &mut str {
        unsafe { str::from_utf8_unchecked_mut(&mut self.0) }
    }
}

impl<const L: usize> ops::IndexMut<ops::RangeInclusive<usize>> for String<L> {
    #[inline]
    fn index_mut(&mut self, index: ops::RangeInclusive<usize>) -> &mut str {
        IndexMut::index_mut(&mut **self, index)
    }
}

impl<const L: usize> ops::IndexMut<ops::RangeToInclusive<usize>> for String<L> {
    #[inline]
    fn index_mut(&mut self, index: ops::RangeToInclusive<usize>) -> &mut str {
        IndexMut::index_mut(&mut **self, index)
    }
}

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

    #[test]
    fn deref() {
        let s_ = "abcde";
        let s = String::<5>::try_from(s_).unwrap();

        assert_eq!(s_, s);
    }

    #[test]
    fn slice() {
        let s = String::<3>::try_from("abc").unwrap();

        assert_eq!(&s[..2], "ab");
    }

    #[test]
    fn eq_impls() {
        let s_ = "abcde";
        let s = String::<5>::try_from(s_).unwrap();

        assert_eq!(s_, s);

        let s_ = s_.to_owned();
        assert_eq!(s_, s);
    }
}