use core::{
borrow::Borrow,
fmt, hash,
mem::{self, size_of, ManuallyDrop, MaybeUninit},
ptr::{self, NonNull},
slice,
};
use crate::alloc::{alloc, string::String};
use crate::Flag;
pub type FlagSlice = UmbraSlice<Flag, { prefix_len::<Flag>() }, { suffix_len::<Flag>() }>;
const fn prefix_len<T>() -> usize {
(size_of::<usize>() - size_of::<u16>()) / size_of::<T>()
}
const fn suffix_len<T>() -> usize {
size_of::<usize>() / size_of::<T>()
}
#[repr(C)]
pub struct UmbraSlice<T: Copy, const PREFIX_LEN: usize, const SUFFIX_LEN: usize> {
len: u16,
prefix: [MaybeUninit<T>; PREFIX_LEN],
trailing: Trailing<T, SUFFIX_LEN>,
}
#[repr(C)]
union Trailing<T: Copy, const SUFFIX_LEN: usize> {
suffix: [MaybeUninit<T>; SUFFIX_LEN],
ptr: ManuallyDrop<NonNull<T>>,
}
impl<T: Copy, const PREFIX_LEN: usize, const SUFFIX_LEN: usize>
UmbraSlice<T, PREFIX_LEN, SUFFIX_LEN>
{
const INLINE_LEN: u16 = (PREFIX_LEN + SUFFIX_LEN) as u16;
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn as_slice(&self) -> &[T] {
let ptr = if self.len <= Self::INLINE_LEN {
let ptr = ptr_from_ref(self);
unsafe { ptr::addr_of!((*ptr).prefix) }.cast()
} else {
unsafe { self.trailing.ptr }.as_ptr()
};
unsafe { slice::from_raw_parts(ptr, self.len()) }
}
fn copy_slice(source: &[T]) -> NonNull<T> {
let layout = Self::layout(source.len());
let nullable = unsafe { alloc::alloc(layout) };
let ptr = match NonNull::new(nullable) {
Some(ptr) => ptr.cast(),
None => alloc::handle_alloc_error(layout),
};
unsafe {
ptr::copy_nonoverlapping(source.as_ptr(), ptr.as_ptr(), source.len());
}
ptr
}
fn layout(len: usize) -> alloc::Layout {
alloc::Layout::array::<T>(len)
.expect("a valid layout for an array")
.pad_to_align()
}
}
impl<T: Copy + Ord + Eq, const PREFIX_LEN: usize, const SUFFIX_LEN: usize>
UmbraSlice<T, PREFIX_LEN, SUFFIX_LEN>
{
pub fn sorted_contains(&self, t: &T) -> bool {
if self.len <= Self::INLINE_LEN {
let ptr = unsafe { ptr::addr_of!((*ptr_from_ref(self)).prefix) }.cast();
let slice = unsafe { slice::from_raw_parts(ptr, self.len()) };
slice.contains(t)
} else {
let ptr = unsafe { self.trailing.ptr }.as_ptr();
let slice = unsafe { slice::from_raw_parts(ptr, self.len()) };
slice.binary_search(t).is_ok()
}
}
}
impl<T: Copy, const PREFIX_LEN: usize, const SUFFIX_LEN: usize> Drop
for UmbraSlice<T, PREFIX_LEN, SUFFIX_LEN>
{
fn drop(&mut self) {
if self.len > Self::INLINE_LEN {
let ptr = unsafe { self.trailing.ptr }.as_ptr();
let layout = Self::layout(self.len());
unsafe { alloc::dealloc(ptr.cast(), layout) }
}
}
}
impl<T: Copy, const PREFIX_LEN: usize, const SUFFIX_LEN: usize> Clone
for UmbraSlice<T, PREFIX_LEN, SUFFIX_LEN>
{
fn clone(&self) -> Self {
let trailing = if self.len <= Self::INLINE_LEN {
let suffix = unsafe { self.trailing.suffix };
Trailing { suffix }
} else {
let ptr = ManuallyDrop::new(Self::copy_slice(self.as_slice()));
Trailing { ptr }
};
Self {
len: self.len,
prefix: self.prefix,
trailing,
}
}
}
impl<T: Copy, const PREFIX_LEN: usize, const SUFFIX_LEN: usize> Default
for UmbraSlice<T, PREFIX_LEN, SUFFIX_LEN>
{
fn default() -> Self {
Self {
len: 0,
prefix: [MaybeUninit::zeroed(); PREFIX_LEN],
trailing: Trailing {
suffix: [MaybeUninit::zeroed(); SUFFIX_LEN],
},
}
}
}
#[derive(Debug)]
pub struct TooLongError;
impl fmt::Display for TooLongError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("input slice was too long (longer than u16::MAX)")
}
}
impl<T: Copy, const PREFIX_LEN: usize, const SUFFIX_LEN: usize> TryFrom<&[T]>
for UmbraSlice<T, PREFIX_LEN, SUFFIX_LEN>
{
type Error = TooLongError;
fn try_from(source: &[T]) -> Result<Self, Self::Error> {
fn copy_to_slice<U: Copy>(this: &mut [MaybeUninit<U>], src: &[U]) {
let uninit_src = unsafe { mem::transmute::<&[U], &[MaybeUninit<U>]>(src) };
this.copy_from_slice(uninit_src);
}
if source.len() > u16::MAX as usize {
return Err(TooLongError);
}
let len = source.len();
let mut prefix = [MaybeUninit::zeroed(); PREFIX_LEN];
if len as u16 <= Self::INLINE_LEN {
let mut suffix = [MaybeUninit::zeroed(); SUFFIX_LEN];
if len <= PREFIX_LEN {
copy_to_slice(&mut prefix[..len], source);
} else {
copy_to_slice(&mut prefix, &source[..PREFIX_LEN]);
copy_to_slice(&mut suffix[..len - PREFIX_LEN], &source[PREFIX_LEN..]);
}
Ok(Self {
len: len as u16,
prefix,
trailing: Trailing { suffix },
})
} else {
copy_to_slice(&mut prefix, &source[..PREFIX_LEN]);
let ptr = ManuallyDrop::new(Self::copy_slice(source));
Ok(Self {
len: len as u16,
prefix,
trailing: Trailing { ptr },
})
}
}
}
impl<T: Copy, const PREFIX_LEN: usize, const SUFFIX_LEN: usize> PartialEq<Self>
for UmbraSlice<T, PREFIX_LEN, SUFFIX_LEN>
{
fn eq(&self, other: &Self) -> bool {
let self_len_and_prefix = ptr_from_ref(self).cast::<usize>();
let other_len_and_prefix = ptr_from_ref(other).cast::<usize>();
if unsafe { *self_len_and_prefix != *other_len_and_prefix } {
return false;
}
if self.len <= Self::INLINE_LEN {
let self_ptr = ptr_from_ref(self);
let self_suffix = unsafe { ptr::addr_of!((*self_ptr).trailing.suffix) }.cast::<usize>();
let other_ptr = ptr_from_ref(other);
let other_suffix =
unsafe { ptr::addr_of!((*other_ptr).trailing.suffix) }.cast::<usize>();
unsafe { *self_suffix == *other_suffix }
} else {
let suffix_len = self.len() - PREFIX_LEN;
let n_bytes = suffix_len * size_of::<T>();
unsafe {
memcmp(
self.trailing.ptr.as_ptr().add(PREFIX_LEN).cast(),
other.trailing.ptr.as_ptr().add(PREFIX_LEN).cast(),
n_bytes,
) == 0
}
}
}
}
extern "C" {
fn memcmp(s1: *const u8, s2: *const u8, n: usize) -> core::ffi::c_int;
}
const fn ptr_from_ref<T: ?Sized>(r: &T) -> *const T {
r
}
impl<T: Copy + Eq, const PREFIX_LEN: usize, const SUFFIX_LEN: usize> Eq
for UmbraSlice<T, PREFIX_LEN, SUFFIX_LEN>
{
}
unsafe impl<T: Copy, const PREFIX_LEN: usize, const SUFFIX_LEN: usize> Send
for UmbraSlice<T, PREFIX_LEN, SUFFIX_LEN>
{
}
unsafe impl<T: Copy, const PREFIX_LEN: usize, const SUFFIX_LEN: usize> Sync
for UmbraSlice<T, PREFIX_LEN, SUFFIX_LEN>
{
}
#[derive(Clone, PartialEq, Eq)]
pub struct UmbraString(U8UmbraSlice);
type U8UmbraSlice = UmbraSlice<u8, { prefix_len::<u8>() }, { suffix_len::<u8>() }>;
impl UmbraString {
pub fn as_bytes(&self) -> &[u8] {
self.0.as_slice()
}
pub fn as_str(&self) -> &str {
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
}
}
impl AsRef<str> for UmbraString {
fn as_ref(&self) -> &str {
self.as_str()
}
}
impl Borrow<str> for UmbraString {
fn borrow(&self) -> &str {
self.as_ref()
}
}
impl hash::Hash for UmbraString {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.as_ref().hash(state)
}
}
impl PartialEq<str> for UmbraString {
fn eq(&self, other: &str) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl fmt::Debug for UmbraString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_ref().fmt(f)
}
}
impl From<&str> for UmbraString {
fn from(s: &str) -> Self {
assert!(s.len() <= u16::MAX as usize);
Self(s.as_bytes().try_into().unwrap())
}
}
impl From<String> for UmbraString {
fn from(s: String) -> Self {
s.as_str().into()
}
}
#[cfg(test)]
mod test {
use super::*;
type U16UmbraSlice = UmbraSlice<u16, { prefix_len::<u16>() }, { suffix_len::<u16>() }>;
#[test]
fn sizing() {
use crate::alloc::boxed::Box;
assert_eq!(size_of::<U16UmbraSlice>(), 2 * size_of::<usize>());
assert_eq!(size_of::<UmbraString>(), 2 * size_of::<usize>());
assert_eq!(size_of::<Box<str>>(), 2 * size_of::<usize>());
assert_eq!(size_of::<Box<[crate::Flag]>>(), 2 * size_of::<usize>());
}
impl fmt::Debug for U16UmbraSlice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.len <= Self::INLINE_LEN {
f.debug_struct("U16UmbraSlice")
.field("len", &self.len)
.field("prefix", unsafe {
&mem::transmute::<&[MaybeUninit<u16>], &[u16]>(&self.prefix)
})
.field("suffix", unsafe {
&mem::transmute::<&[MaybeUninit<u16>], &[u16]>(&self.trailing.suffix)
})
.finish()
} else {
todo!()
}
}
}
#[test]
fn eq() {
assert_eq!(
U16UmbraSlice::try_from([1u16].as_slice()).unwrap(),
U16UmbraSlice::try_from([1u16].as_slice()).unwrap(),
);
assert_eq!(UmbraString::from(""), UmbraString::from(""));
assert_eq!(UmbraString::from("foo"), UmbraString::from("foo"),);
}
#[test]
fn partial_eq_str() {
assert_eq!(UmbraString::from("hello"), *"hello");
assert_eq!(UmbraString::from("foobarbaz"), *"foobarbaz");
assert_eq!(UmbraString::from("hellohellohello"), *"hellohellohello");
}
#[test]
fn clone() {
let s = UmbraString::from("hellohellohello");
assert_eq!(s.clone(), s);
assert_eq!(s.clone(), *"hellohellohello");
let s = UmbraString::from("foo");
assert_eq!(s.clone(), s);
assert_eq!(s.clone(), *"foo");
}
#[test]
fn hash() {
use core::hash::{BuildHasher, Hash, Hasher};
fn hash_once<T: Hash, S: BuildHasher>(hash_builder: &S, val: &T) -> u64 {
let mut state = hash_builder.build_hasher();
val.hash(&mut state);
state.finish()
}
let hasher = crate::DefaultHashBuilder::default();
let s = UmbraString::from("hellohellohello");
assert_eq!(hash_once(&hasher, &s), hash_once(&hasher, &s));
assert_eq!(hash_once(&hasher, &s), hash_once(&hasher, &s.clone()));
assert_eq!(
hash_once(&hasher, &s),
hash_once(&hasher, &UmbraString::from("hellohellohello"))
);
assert_eq!(
hash_once(&hasher, &s),
hash_once(&hasher, &"hellohellohello")
);
}
}