use std::{
fmt::{self, Display},
ops::{Add, AddAssign, Deref},
};
use crate::assert_unchecked;
#[derive(Clone, Copy)]
#[repr(C)]
pub struct InlineString<const CAPACITY: usize, Len: UnsignedInt> {
bytes: [u8; CAPACITY],
len: Len,
_align: [usize; 0],
}
impl<const CAPACITY: usize, Len: UnsignedInt> InlineString<CAPACITY, Len> {
const ASSERTS: () = {
assert!(CAPACITY <= Len::MAX_USIZE, "`CAPACITY` is too large for `Len`");
assert!(
(CAPACITY + size_of::<Len>()).is_multiple_of(size_of::<usize>()),
"`CAPACITY + size_of::<Len>()` is not a multiple of `size_of::<usize>()`"
);
};
#[inline]
pub fn new() -> Self {
const { Self::ASSERTS };
Self { bytes: [0; CAPACITY], len: Len::ZERO, _align: [] }
}
#[expect(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Self {
const { Self::ASSERTS };
let mut bytes = [0; CAPACITY];
let slice = &mut bytes[..s.len()];
slice.copy_from_slice(s.as_bytes());
Self { bytes, len: Len::from_usize(s.len()), _align: [] }
}
#[inline]
pub fn push(&mut self, byte: u8) {
assert!(self.len.to_usize() < CAPACITY);
assert!(byte.is_ascii());
unsafe { self.push_unchecked(byte) }
}
#[inline]
pub unsafe fn push_unchecked(&mut self, byte: u8) {
debug_assert!(self.len.to_usize() < CAPACITY);
debug_assert!(byte.is_ascii());
unsafe { *self.bytes.get_unchecked_mut(self.len.to_usize()) = byte };
self.len += Len::from_usize(1);
}
#[inline]
pub fn len(&self) -> Len {
self.len
}
#[inline]
pub fn len_usize(&self) -> usize {
self.len.to_usize()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == Len::ZERO
}
#[inline]
pub fn as_str(&self) -> &str {
unsafe {
assert_unchecked!(self.len.to_usize() <= CAPACITY);
let slice = &self.bytes[..self.len.to_usize()];
std::str::from_utf8_unchecked(slice)
}
}
#[inline]
pub fn as_mut_str(&mut self) -> &mut str {
unsafe {
assert_unchecked!(self.len.to_usize() <= CAPACITY);
let slice = &mut self.bytes[..self.len.to_usize()];
std::str::from_utf8_unchecked_mut(slice)
}
}
}
impl<const CAPACITY: usize, Len: UnsignedInt> Default for InlineString<CAPACITY, Len> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<const CAPACITY: usize, Len: UnsignedInt> Deref for InlineString<CAPACITY, Len> {
type Target = str;
#[inline]
fn deref(&self) -> &str {
self.as_str()
}
}
impl<const CAPACITY: usize, Len: UnsignedInt> Display for InlineString<CAPACITY, Len> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[expect(private_bounds)]
pub trait UnsignedInt: Copy + PartialEq + Eq + Add + AddAssign + Sealed {
const ZERO: Self;
const MAX_USIZE: usize;
fn to_usize(self) -> usize;
fn from_usize(n: usize) -> Self;
}
trait Sealed {}
macro_rules! impl_unsigned_int {
($ty:ident) => {
#[allow(clippy::cast_possible_truncation, clippy::allow_attributes)]
impl UnsignedInt for $ty {
const ZERO: Self = 0;
const MAX_USIZE: usize = $ty::MAX as usize;
#[inline(always)]
fn to_usize(self) -> usize {
self as usize
}
#[inline(always)]
fn from_usize(n: usize) -> Self {
n.try_into().unwrap()
}
}
impl Sealed for $ty {}
};
}
impl_unsigned_int!(u8);
impl_unsigned_int!(u16);
impl_unsigned_int!(u32);
#[cfg(target_pointer_width = "64")]
impl_unsigned_int!(u64);
impl_unsigned_int!(usize);