use std::cmp::Ordering;
use std::ffi::c_void;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::ptr::NonNull;
use triomphe::ThinArc;
#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)]
pub struct IsizeInPtr {
ptr: NonNull<c_void>,
}
impl IsizeInPtr {
pub const MAX: isize = isize::MAX >> 1;
pub const MIN: isize = isize::MIN >> 1;
const TAG_MASK: isize = 1;
fn from_ptr(ptr: *const c_void) -> Option<Self> {
if (ptr as isize & Self::TAG_MASK) == 0 {
None
} else {
Some(unsafe { Self::new_unchecked(ptr) })
}
}
fn from_isize_unchecked(value: isize) -> Self {
let tagged = (value << 1) | Self::TAG_MASK;
unsafe { Self::new_unchecked(tagged as *const c_void) }
}
unsafe fn new_unchecked(ptr: *const c_void) -> Self {
IsizeInPtr {
ptr: unsafe { NonNull::from_ref(&*ptr) },
}
}
pub fn get(&self) -> isize {
(self.ptr.as_ptr() as isize) >> 1
}
}
impl Default for IsizeInPtr {
fn default() -> Self {
Self::from_isize_unchecked(0)
}
}
impl From<IsizeInPtr> for isize {
fn from(value: IsizeInPtr) -> isize {
value.get()
}
}
impl TryFrom<isize> for IsizeInPtr {
type Error = TryFromIsizeError<isize>;
fn try_from(value: isize) -> Result<IsizeInPtr, Self::Error> {
if (value <= Self::MAX) && (value >= Self::MIN) {
Ok(Self::from_isize_unchecked(value))
} else {
Err(TryFromIsizeError { original: value })
}
}
}
macro_rules! impl_try_from_for_integral {
($($t:ty),*) => {
$(
impl TryFrom<$t> for IsizeInPtr {
type Error = TryFromIsizeError<$t>;
fn try_from(value: $t) -> Result<Self, Self::Error> {
isize::try_from(value).ok()
.and_then(|n: isize| IsizeInPtr::try_from(n).ok())
.ok_or(TryFromIsizeError{ original: value })
}
}
)*
};
}
impl_try_from_for_integral!(i8, i16, i32, i64, i128);
#[derive(Debug, Clone, Copy)]
pub struct TryFromIsizeError<N> {
pub original: N,
}
impl<N: std::fmt::Display> std::fmt::Display for TryFromIsizeError<N> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Value {} doesn't fit inside taggeed isize bounds [{}, {}]",
self.original,
IsizeInPtr::MIN,
IsizeInPtr::MAX
)
}
}
impl<N: std::fmt::Display + std::fmt::Debug> std::error::Error for TryFromIsizeError<N> {}
pub struct ThinArcOrInt<H, T> {
raw: NonNull<c_void>,
_marker: PhantomData<ThinArc<H, T>>,
}
unsafe impl<H, T> Send for ThinArcOrInt<H, T> where ThinArc<H, T>: Send {}
unsafe impl<H, T> Sync for ThinArcOrInt<H, T> where ThinArc<H, T>: Sync {}
impl<H, T> ThinArcOrInt<H, T> {
pub fn from_isize(val: IsizeInPtr) -> Self {
Self {
raw: val.ptr,
_marker: PhantomData,
}
}
pub fn from_arc(arc: ThinArc<H, T>) -> Self {
let ptr = ThinArc::into_raw(arc);
debug_assert!(
IsizeInPtr::from_ptr(ptr).is_none(),
"Pointer must be 2-aligned!"
);
Self {
raw: unsafe { NonNull::from_ref(&*ptr) },
_marker: PhantomData,
}
}
pub fn from_convertible<U, E>(value: U, slice: &[T]) -> Self
where
U: TryInto<IsizeInPtr, Error = E> + Into<H>,
E: Into<H>,
T: Copy,
{
if slice.is_empty() {
match value.try_into() {
Ok(i) => Self::from_isize(i),
Err(e) => {
Self::from_arc(ThinArc::from_header_and_iter(e.into(), std::iter::empty()))
}
}
} else {
Self::from_arc(ThinArc::from_header_and_slice(value.into(), slice))
}
}
pub fn has_number(&self) -> bool {
self.as_isize().is_some()
}
pub fn has_ref(&self) -> bool {
!self.has_number()
}
pub fn as_isize(&self) -> Option<isize> {
IsizeInPtr::from_ptr(self.raw.as_ptr()).map(|i| i.into())
}
pub fn as_arc(&self) -> Option<&ThinArc<H, T>> {
if self.has_ref() {
unsafe { Some(self.as_arc_internal()) }
} else {
None
}
}
unsafe fn as_arc_internal(&self) -> &ThinArc<H, T> {
&*(&self.raw as *const NonNull<c_void> as *const ThinArc<H, T>)
}
}
impl<H, T> Default for ThinArcOrInt<H, T> {
fn default() -> Self {
Self::from_isize(Default::default())
}
}
impl<H, T> Drop for ThinArcOrInt<H, T> {
fn drop(&mut self) {
if self.has_ref() {
let _arc = unsafe { ThinArc::<H, T>::from_raw(self.raw.as_ptr()) };
}
}
}
impl<H, T> Clone for ThinArcOrInt<H, T> {
fn clone(&self) -> Self {
if self.has_number() {
Self {
raw: self.raw,
_marker: PhantomData,
}
} else {
let arc = unsafe { self.as_arc_internal() };
let cloned_arc = arc.clone();
Self::from_arc(cloned_arc)
}
}
}
impl<H, T> PartialEq for ThinArcOrInt<H, T>
where
H: PartialEq,
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self.as_isize(), other.as_isize()) {
(Some(s), Some(o)) => s == o,
(None, None) => unsafe { self.as_arc_internal() == other.as_arc_internal() },
_ => false,
}
}
}
impl<H, T> Eq for ThinArcOrInt<H, T>
where
H: Eq,
T: Eq,
{
}
impl<H, T> PartialOrd for ThinArcOrInt<H, T>
where
H: PartialOrd,
T: PartialOrd,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self.as_isize(), other.as_isize()) {
(Some(s), Some(o)) => s.partial_cmp(&o),
(None, None) => unsafe { self.as_arc_internal().partial_cmp(other.as_arc_internal()) },
(Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater),
}
}
}
impl<H, T> Ord for ThinArcOrInt<H, T>
where
H: Ord,
T: Ord,
{
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other)
.expect("ThinArc::partial_cmp returned `None`")
}
}
impl<H, T> Hash for ThinArcOrInt<H, T>
where
H: Hash,
T: Hash,
{
fn hash<S: Hasher>(&self, state: &mut S) {
if let Some(num) = self.as_isize() {
0.hash(state);
num.hash(state);
} else {
1.hash(state);
unsafe { self.as_arc_internal().hash(state) };
}
}
}
impl<H: fmt::Debug, T: fmt::Debug> fmt::Debug for ThinArcOrInt<H, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(num) = self.as_isize() {
f.debug_tuple("ThinArcOrInt::Number").field(&num).finish()
} else {
f.debug_tuple("ThinArcOrInt::ThinArc")
.field(self.as_arc().unwrap())
.finish()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn from_isize(value: isize) -> ThinArcOrInt<(), ()> {
ThinArcOrInt::<(), ()>::from_isize(
IsizeInPtr::try_from(value).expect("Out of allowed bounds"),
)
}
#[test]
fn test_size() {
assert_eq!(
std::mem::size_of::<ThinArcOrInt<(), String>>(),
std::mem::size_of::<usize>()
);
}
#[test]
fn test_clone_and_drop() {
let arc = ThinArc::from_header_and_slice((), "Shared data".as_bytes());
let val1 = ThinArcOrInt::from_arc(arc);
let val2 = val1.clone();
assert_eq!(&val1.as_arc().unwrap().slice, "Shared data".as_bytes());
assert!(std::ptr::eq(
&val1.as_arc().unwrap().slice,
&val2.as_arc().unwrap().slice
));
}
#[test]
fn test_option_size_optimization() {
assert_eq!(
std::mem::size_of::<ThinArcOrInt<(), String>>(),
std::mem::size_of::<usize>()
);
assert_eq!(
std::mem::size_of::<Option<ThinArcOrInt<(), String>>>(),
std::mem::size_of::<usize>()
);
assert_ne!(None, Some(from_isize(0)));
}
#[test]
fn test_negative_numbers() {
let negative = from_isize(-42);
assert!(negative.has_number());
assert_eq!(negative.as_isize(), Some(-42));
}
#[test]
fn test_max_number() {
let negative = from_isize(IsizeInPtr::MAX);
assert!(negative.has_number());
assert_eq!(negative.as_isize(), Some(IsizeInPtr::MAX));
}
#[test]
fn test_min_number() {
let negative = from_isize(IsizeInPtr::MIN);
assert!(negative.has_number());
assert_eq!(negative.as_isize(), Some(IsizeInPtr::MIN));
}
#[test]
fn test_thin_arc() {
let arc = ThinArc::from_header_and_slice((), b"Hello Rust");
let val = ThinArcOrInt::from_arc(arc);
assert!(val.has_ref());
assert!(!val.has_number());
assert_eq!(
&val.as_arc().expect("Must be Arc, not isize").slice,
b"Hello Rust"
);
}
}