use core::{
borrow::Borrow,
cmp::{Eq, Ord, PartialEq, PartialOrd},
convert::TryFrom,
fmt,
hash::{Hash, Hasher},
ops::{self, Deref, DerefMut, Index, IndexMut},
str,
};
use self::builder::StringBuilder;
use crate::error::Error;
pub mod builder;
#[cfg(feature = "rkyv-derive")]
mod rkyv;
#[cfg(feature = "serde-derive")]
pub mod serde;
#[derive(Copy, Clone, Eq, PartialOrd, Ord)]
pub struct String<const L: usize>(pub(crate) [u8; L]);
impl<const L: usize> String<L> {
pub const fn empty() -> Self {
Self([b' '; L])
}
pub fn try_from_str_padded(s: impl AsRef<str>) -> Result<Self, Error> {
Self::try_from_bytes_padded(s.as_ref().as_bytes())
}
pub fn try_from_bytes_padded(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
let bytes = bytes.as_ref();
if bytes.len() > L {
return Err(Error::new(L, 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()
}
pub fn into_bytes(self) -> [u8; L] {
self.0
}
pub fn is_empty(&self) -> bool {
self.0.iter().all(|&x| x == b' ')
}
pub fn starts_empty(&self) -> bool {
self.0[0] == b' '
}
}
impl<const L: usize> String<L> {
pub fn from_other<const K: usize>(other: impl Into<String<K>>) -> Self {
const {
assert!(
K <= L,
"String<L> can only be created from String<K> if K <= L"
);
}
let mut buf = [b' '; L];
buf[..K].copy_from_slice(other.into().as_bytes());
String(buf)
}
pub fn into_other<const K: usize>(self) -> String<K> {
String::<K>::from_other(self)
}
pub fn trim_down<const K: usize>(self) -> String<K> {
const {
assert!(
K < L,
"String<L> can only be trimmed down to String<K> if K < L"
);
}
let mut buf = [b' '; K];
buf.copy_from_slice(&self.as_bytes()[..K]);
String(buf)
}
}
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> fmt::Display for String<L> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::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> AsRef<[u8]> for String<L> {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<const L: usize> AsRef<str> for String<L> {
fn as_ref(&self) -> &str {
self
}
}
impl<const L: usize> From<[u8; L]> for String<L> {
fn from(buf: [u8; L]) -> Self {
String(buf)
}
}
impl<const L: usize> From<&[u8; L]> for String<L> {
fn from(buf: &[u8; L]) -> Self {
String(*buf)
}
}
impl<const L: usize> Hash for String<L> {
#[inline]
fn hash<H: Hasher>(&self, hasher: &mut H) {
(**self).hash(hasher)
}
}
impl<const L: usize> TryFrom<&str> for String<L> {
type Error = crate::error::Error;
fn try_from(s: &str) -> Result<Self, Self::Error> {
if s.len() != L {
return Err(Error::new(L, s.len()));
}
let mut res = Self::empty();
res.0.copy_from_slice(s.as_bytes());
Ok(res)
}
}
impl<const L: usize> Borrow<str> for String<L> {
#[inline]
fn borrow(&self) -> &str {
self
}
}
impl<const L: usize> PartialEq for String<L> {
#[inline]
fn eq(&self, other: &String<L>) -> bool {
PartialEq::eq(&self[..], &other[..])
}
}
impl<const L: usize> PartialEq<str> for String<L> {
#[inline]
fn eq(&self, other: &str) -> bool {
PartialEq::eq(&self[..], other)
}
}
impl<const L: usize> PartialEq<&str> for String<L> {
#[inline]
fn eq(&self, other: &&str) -> bool {
PartialEq::eq(&self[..], &other[..])
}
}
impl<const L: usize> PartialEq<String<L>> for &str {
#[inline]
fn eq(&self, other: &String<L>) -> bool {
PartialEq::eq(&self[..], &other[..])
}
}
impl<const L: usize> PartialEq<std::string::String> for String<L> {
#[inline]
fn eq(&self, other: &std::string::String) -> bool {
PartialEq::eq(&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[..])
}
}
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::*;
use std::collections::HashSet;
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
#[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);
}
#[test]
fn hash_set_contains() {
let s_ = "abcde";
let s = String::<5>::try_from(s_).unwrap();
assert_eq!(<String<5> as Borrow<str>>::borrow(&s), s_);
let mut hasher = DefaultHasher::new();
s_.hash(&mut hasher);
let s_hash = hasher.finish();
let mut hasher = DefaultHasher::new();
s.hash(&mut hasher);
let ss_hash = hasher.finish();
assert_eq!(s_hash, ss_hash);
let set = HashSet::from([s]);
assert!(set.contains(s_));
let s_ = s_.to_owned();
assert!(set.contains(s_.as_str()));
}
}