diff --git a/src/lib.rs b/src/lib.rs index 14ea2b3..8190c34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ //! # use std::convert::{TryFrom, TryInto}; //! use std::mem::ManuallyDrop; //! -//! use stuff::{StuffedPtr, StuffingStrategy, Either}; +//! use stuff::{StuffedPtr, StuffingStrategy, Unstuffed}; //! //! // Create a unit struct for our strategy //! struct NanBoxStrategy; @@ -49,11 +49,11 @@ //! unsafe { std::mem::transmute(inner) } // both are 64 bit POD's //! } //! -//! unsafe fn extract(data: u64) -> Either> { +//! fn extract(data: u64) -> Unstuffed { //! if (data & QNAN) != QNAN { -//! Either::Other(ManuallyDrop::new(f64::from_bits(data))) +//! Unstuffed::Other(f64::from_bits(data)) //! } else { -//! Either::Ptr((data & !(SIGN_BIT | QNAN)).try_into().unwrap()) +//! Unstuffed::Ptr((data & !(SIGN_BIT | QNAN)).try_into().unwrap()) //! } //! } //! @@ -70,16 +70,16 @@ //! type Value = StuffedPtr; //! //! let float: Value = StuffedPtr::new_other(123.5); -//! assert_eq!(float.copy_other(), Some(123.5)); +//! assert_eq!(float.other(), Some(123.5)); //! //! let object: Object = HashMap::from([("a".to_owned(), 457)]); //! let boxed = Box::new(object); //! let ptr: Value = StuffedPtr::new_ptr(Box::into_raw(boxed)); //! -//! let object = unsafe { &*ptr.get_ptr().unwrap() }; +//! let object = unsafe { &*ptr.ptr().unwrap() }; //! assert_eq!(object.get("a"), Some(&457)); //! -//! drop(unsafe { Box::from_raw(ptr.get_ptr().unwrap()) }); +//! drop(unsafe { Box::from_raw(ptr.ptr().unwrap()) }); //! //! // be careful, `ptr` is a dangling pointer now! //! ``` @@ -97,12 +97,11 @@ use core::{ fmt::{Debug, Formatter}, hash::{Hash, Hasher}, marker::PhantomData, - mem::ManuallyDrop, }; use sptr::Strict; -pub use crate::{backend::Backend, either::Either, guard::Guard, strategy::StuffingStrategy}; +pub use crate::{backend::Backend, either::Unstuffed, strategy::StuffingStrategy}; /// A union of a pointer or some `other` data, bitpacked into a value with the size depending on /// `B`. It defaults to `usize`, meaning pointer sized, but `u64` and `u128` are also provided @@ -114,13 +113,12 @@ pub use crate::{backend::Backend, either::Either, guard::Guard, strategy::Stuffi /// /// For a usage example, view the crate level documentation. /// -/// This pointer does *not* drop `other` data, [`StuffedPtr::into_other`] can be used if that is required. -/// -/// `StuffedPtr` implements most traits like `Clone`, `PartialEq` or `Copy` if the `other` type does. +/// `StuffedPtr` implements most traits like `Hash` or `PartialEq` if the `other` type does. +/// It's also always `Copy`, and therefore requires the other type to be `Copy` as well. /// /// This type is guaranteed to be `#[repr(transparent)]` to a `B::Stored`. #[repr(transparent)] -pub struct StuffedPtr(B::Stored, PhantomData>) +pub struct StuffedPtr(B::Stored, PhantomData>) where B: Backend; @@ -146,40 +144,23 @@ where } /// Get the pointer data, or `None` if it contains `other` data - pub fn get_ptr(&self) -> Option<*mut T> { + pub fn ptr(&self) -> Option<*mut T> { let (provenance, stored) = B::get_ptr(self.0); - let addr = unsafe { S::extract(stored).ptr()? }; + let addr = S::extract(stored).ptr()?; Some(Strict::with_addr(provenance.cast::(), addr)) } - /// Get owned `other` data from this, or `None` if it contains pointer data - pub unsafe fn into_other(self) -> Option { - let this = ManuallyDrop::new(self); - // SAFETY: `self` is consumed and forgotten after this call - let other = this.get_other(); - other.map(|md| ManuallyDrop::into_inner(md)) - } - /// Get `other` data from this, or `None` if it contains pointer data - pub unsafe fn get_other(&self) -> Option> { + pub fn other(&self) -> Option { let data = self.addr(); - unsafe { S::extract(data).other() } + S::extract(data).other() } - pub fn into_inner(self) -> Either<*mut T, S::Other> { + /// Get out the unstuffed enum representation + pub fn unstuff(&self) -> Unstuffed<*mut T, S::Other> { let (provenance, stored) = B::get_ptr(self.0); - let either = unsafe { S::extract(stored) }; - either - .map_ptr(|addr| Strict::with_addr(provenance.cast::(), addr)) - .map_other(|other| ManuallyDrop::into_inner(other)) - } - - pub fn get(&self) -> Either<*mut T, Guard<'_, S::Other>> { - let (provenance, stored) = B::get_ptr(self.0); - let either = unsafe { S::extract(stored) }; - either - .map_ptr(|addr| Strict::with_addr(provenance.cast::(), addr)) - .map_other(|other| Guard::new(other)) + let either = S::extract(stored); + either.map_ptr(|addr| Strict::with_addr(provenance.cast::(), addr)) } fn addr(&self) -> B { @@ -187,20 +168,6 @@ where } } -/// Extra implementations if the `other` type is `Copy` -impl StuffedPtr -where - S: StuffingStrategy, - S::Other: Copy, - B: Backend, -{ - /// Get `other` data from this, or `None` if it's pointer data - pub fn copy_other(&self) -> Option { - // SAFETY: `S::Other: Copy` - unsafe { self.get_other().map(|other| *other) } - } -} - impl Debug for StuffedPtr where S: StuffingStrategy, @@ -208,9 +175,9 @@ where B: Backend, { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self.get() { - Either::Ptr(ptr) => f.debug_tuple("Ptr").field(&ptr).finish(), - Either::Other(other) => f.debug_tuple("Other").field(other.inner()).finish(), + match self.unstuff() { + Unstuffed::Ptr(ptr) => f.debug_tuple("Ptr").field(&ptr).finish(), + Unstuffed::Other(other) => f.debug_tuple("Other").field(&other).finish(), } } } @@ -218,24 +185,16 @@ where impl Clone for StuffedPtr where S: StuffingStrategy, - S::Other: Clone, B: Backend, { fn clone(&self) -> Self { - match self.get() { - Either::Ptr(ptr) => StuffedPtr::new_ptr(ptr), - Either::Other(other) => { - let cloned_other = other.inner().clone(); - Self::new_other(cloned_other) - } - } + *self } } impl Copy for StuffedPtr where S: StuffingStrategy, - S::Other: Copy, B: Backend, { } @@ -247,9 +206,9 @@ where B: Backend, { fn eq(&self, other: &Self) -> bool { - match (self.get(), other.get()) { - (Either::Ptr(a), Either::Ptr(b)) => core::ptr::eq(a, b), - (Either::Other(a), Either::Other(b)) => a.inner() == b.inner(), + match (self.unstuff(), other.unstuff()) { + (Unstuffed::Ptr(a), Unstuffed::Ptr(b)) => core::ptr::eq(a, b), + (Unstuffed::Other(a), Unstuffed::Other(b)) => a == b, _ => false, } } @@ -270,25 +229,29 @@ where B: Backend, { fn hash(&self, state: &mut H) { - match self.get() { - Either::Ptr(ptr) => { + match self.unstuff() { + Unstuffed::Ptr(ptr) => { ptr.hash(state); } - Either::Other(other) => { - other.inner().hash(state); + Unstuffed::Other(other) => { + other.hash(state); } } } } mod either { + /// The enum representation of a `StuffedPtr` #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] - pub enum Either { + pub enum Unstuffed { + /// The pointer or pointer address Ptr(P), + /// The other type Other(O), } - impl Either { + impl Unstuffed { + /// Get the pointer, or `None` if it's the other pub fn ptr(&self) -> Option

{ match *self { Self::Ptr(ptr) => Some(ptr), @@ -296,6 +259,7 @@ mod either { } } + /// Get the other type, or `None` if it's the pointer pub fn other(self) -> Option { match self { Self::Ptr(_) => None, @@ -303,126 +267,13 @@ mod either { } } - pub fn map_ptr(self, f: impl FnOnce(P) -> U) -> Either { + /// Maps the pointer type if it's a pointer, or does nothing if it's other + pub fn map_ptr(self, f: impl FnOnce(P) -> U) -> Unstuffed { match self { - Self::Ptr(ptr) => Either::Ptr(f(ptr)), - Self::Other(other) => Either::Other(other), + Self::Ptr(ptr) => Unstuffed::Ptr(f(ptr)), + Self::Other(other) => Unstuffed::Other(other), } } - - pub fn map_other(self, f: impl FnOnce(O) -> U) -> Either { - match self { - Self::Ptr(ptr) => Either::Ptr(ptr), - Self::Other(other) => Either::Other(f(other)), - } - } - } -} - -mod guard { - use core::{fmt::Debug, marker::PhantomData, mem::ManuallyDrop, ops::Deref}; - - use self::no_alias::AllowAlias; - - mod no_alias { - use core::{fmt::Debug, hash::Hash, mem::MaybeUninit, ops::Deref}; - - /// A `T` except with aliasing problems removed - /// - /// this is very sketchy and actually kinda equivalent to whatever ralf wants to add to ManuallyDrop - /// so idk but whatever this is a great type - pub struct AllowAlias(MaybeUninit); - - impl AllowAlias { - pub fn new(value: T) -> Self { - Self(MaybeUninit::new(value)) - } - - pub fn into_inner(self) -> T { - unsafe { self.0.assume_init() } - } - } - - impl Debug for AllowAlias { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_tuple("NoAlias").field(&self.0).finish() - } - } - - impl PartialEq for AllowAlias { - fn eq(&self, other: &Self) -> bool { - &*self == &*other - } - } - - impl Eq for AllowAlias {} - - impl PartialOrd for AllowAlias { - fn partial_cmp(&self, other: &Self) -> Option { - self.deref().partial_cmp(&*other) - } - } - - impl Ord for AllowAlias { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.deref().cmp(&*other) - } - } - - impl Hash for AllowAlias { - fn hash(&self, state: &mut H) { - self.deref().hash(state); - } - } - - impl Deref for AllowAlias { - type Target = T; - - fn deref(&self) -> &Self::Target { - unsafe { self.0.assume_init_ref() } - } - } - } - - /// Acts like a `&T` but has to carry around the value. - pub struct Guard<'a, T> { - inner: AllowAlias, - _boo: PhantomData<&'a ()>, - } - - impl<'a, T> Guard<'a, T> { - pub fn new(value: ManuallyDrop) -> Self { - Self { - inner: AllowAlias::new(ManuallyDrop::into_inner(value)), - _boo: PhantomData, - } - } - - /// # Safety - /// Make sure to not violate aliasing with this - pub unsafe fn into_inner(self) -> ManuallyDrop { - ManuallyDrop::new(self.inner.into_inner()) - } - - pub fn inner(&self) -> &T { - &*self - } - } - - impl Deref for Guard<'_, T> { - type Target = T; - - fn deref(&self) -> &Self::Target { - &*self.inner - } - } - - impl Debug for Guard<'_, T> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("NeverDrop") - .field("inner", &*self.inner) - .finish() - } } } @@ -430,19 +281,15 @@ mod guard { mod tests { #![allow(non_snake_case)] - use core::mem; use std::{boxed::Box, format, println}; use paste::paste; use crate::{ - strategy::test_strategies::{EmptyInMax, HasDebug, PanicsInDrop}, + strategy::test_strategies::{EmptyInMax, HasDebug}, Backend, StuffedPtr, StuffingStrategy, }; - // note: the tests mostly use the `PanicsInDrop` type and strategy, to make sure that no - // `other` is ever dropped accidentally. - fn from_box(boxed: Box) -> StuffedPtr where S: StuffingStrategy, @@ -459,7 +306,7 @@ mod tests { unsafe { let boxed = Box::new(1); let stuffed_ptr: StuffedPtr = from_box(boxed); - let ptr = stuffed_ptr.get_ptr().unwrap(); + let ptr = stuffed_ptr.ptr().unwrap(); let boxed = Box::from_raw(ptr); assert_eq!(*boxed, 1); } @@ -469,8 +316,8 @@ mod tests { #[test] fn []() { let stuffed_ptr: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_other(EmptyInMax); - assert!(unsafe { stuffed_ptr.get_other() }.is_some()); - assert!(matches!(stuffed_ptr.copy_other(), Some(EmptyInMax))); + assert!(stuffed_ptr.other().is_some()); + assert!(matches!(stuffed_ptr.other(), Some(EmptyInMax))); } #[test] @@ -480,7 +327,7 @@ mod tests { println!("{stuffed_ptr:?}"); assert!(format!("{stuffed_ptr:?}").starts_with("Ptr(")); - drop(unsafe { Box::from_raw(stuffed_ptr.get_ptr().unwrap()) }); + drop(unsafe { Box::from_raw(stuffed_ptr.ptr().unwrap()) }); let other = HasDebug; let stuffed_ptr: StuffedPtr = StuffedPtr::new_other(other); @@ -494,13 +341,11 @@ mod tests { #[allow(clippy::redundant_clone)] fn []() { let mut unit = (); - let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit); + let stuffed_ptr1: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_ptr(&mut unit); let _ = stuffed_ptr1.clone(); - let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_other(PanicsInDrop); - let stuffed_ptr2 = stuffed_ptr1.clone(); - - mem::forget((stuffed_ptr1, stuffed_ptr2)); + let stuffed_ptr1: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_other(EmptyInMax); + let _ = stuffed_ptr1.clone(); } @@ -508,42 +353,17 @@ mod tests { fn []() { // two pointers let mut unit = (); - let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit); - let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit); + let stuffed_ptr1: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_ptr(&mut unit); + let stuffed_ptr2: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_ptr(&mut unit); assert_eq!(stuffed_ptr1, stuffed_ptr2); - let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit); - let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_other(PanicsInDrop); + let stuffed_ptr1: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_ptr(&mut unit); + let stuffed_ptr2: StuffedPtr<(), EmptyInMax, $backend> = StuffedPtr::new_other(EmptyInMax); assert_ne!(stuffed_ptr1, stuffed_ptr2); - mem::forget(stuffed_ptr2); } - - #[test] - fn []() { - let mut unit = (); - let stuffed_ptr: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_ptr(&mut unit); - // the panicking drop needs not to be called here! - drop(stuffed_ptr); - } - - - #[test] - fn []() { - // make sure that other is never dropped twice - - let stuffed_ptr1: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_other(PanicsInDrop); - let stuffed_ptr2: StuffedPtr<(), PanicsInDrop, $backend> = StuffedPtr::new_other(PanicsInDrop); - - // PartialEq - assert_eq!(stuffed_ptr1, stuffed_ptr2); - // Debug - let _ = format!("{stuffed_ptr1:?}"); - - mem::forget((stuffed_ptr1, stuffed_ptr2)); - } } }; } diff --git a/src/strategy.rs b/src/strategy.rs index 794e881..91b6824 100644 --- a/src/strategy.rs +++ b/src/strategy.rs @@ -1,6 +1,6 @@ -use core::{convert::TryInto, mem::ManuallyDrop}; +use core::convert::TryInto; -use crate::{Backend, Either}; +use crate::{Backend, Unstuffed}; /// A trait that describes how to stuff others and pointers into the pointer sized object. /// @@ -16,14 +16,11 @@ use crate::{Backend, Either}; /// If [`StuffingStrategy::is_other`] returns true for a value, then /// [`StuffingStrategy::extract_other`] *must* return a valid `Other` for that same value. /// -/// [`StuffingStrategy::stuff_other`] *must* consume `inner` and make sure that it's not dropped -/// if it isn't `Copy`. -/// /// For [`StuffingStrategy::stuff_ptr`] and [`StuffingStrategy::extract_ptr`], /// `ptr == extract_ptr(stuff_ptr(ptr))` *must* hold true. pub unsafe trait StuffingStrategy { /// The type of the other. - type Other; + type Other: Copy; /// Stuff other data into a usize that is then put into the pointer. This operation /// must be infallible. @@ -32,7 +29,7 @@ pub unsafe trait StuffingStrategy { /// Extract the pointer data or other data /// # Safety /// `data` must contain data created by [`StuffingStrategy::stuff_other`]. - unsafe fn extract(data: B) -> Either>; + fn extract(data: B) -> Unstuffed; /// Stuff a pointer address into the pointer sized integer. /// @@ -54,8 +51,8 @@ where B::default() } - unsafe fn extract(data: B) -> Either> { - Either::Ptr( + fn extract(data: B) -> Unstuffed { + Unstuffed::Ptr( data.try_into() // note: this can't happen 🤔 .unwrap_or_else(|_| panic!("Pointer value too big for usize")), @@ -70,13 +67,10 @@ where #[cfg(test)] pub(crate) mod test_strategies { - use core::{ - fmt::{Debug, Formatter}, - mem::ManuallyDrop, - }; + use core::fmt::{Debug, Formatter}; use super::StuffingStrategy; - use crate::Either; + use crate::Unstuffed; macro_rules! impl_usize_max_zst { ($ty:ident) => { @@ -90,10 +84,10 @@ pub(crate) mod test_strategies { usize::MAX } - unsafe fn extract(data: usize) -> Either> { + fn extract(data: usize) -> Unstuffed { match data == usize::MAX { - true => Either::Other(ManuallyDrop::new($ty)), - false => Either::Ptr(data), + true => Unstuffed::Other($ty), + false => Unstuffed::Ptr(data), } } @@ -111,10 +105,10 @@ pub(crate) mod test_strategies { u64::MAX } - unsafe fn extract(data: u64) -> Either> { + fn extract(data: u64) -> Unstuffed { match data == u64::MAX { - true => Either::Other(ManuallyDrop::new($ty)), - false => Either::Ptr(data as usize), + true => Unstuffed::Other($ty), + false => Unstuffed::Ptr(data as usize), } } @@ -132,10 +126,10 @@ pub(crate) mod test_strategies { u128::MAX } - unsafe fn extract(data: u128) -> Either> { + fn extract(data: u128) -> Unstuffed { match data == u128::MAX { - true => Either::Other(ManuallyDrop::new($ty)), - false => Either::Ptr(data as usize), + true => Unstuffed::Other($ty), + false => Unstuffed::Ptr(data as usize), } } @@ -146,11 +140,12 @@ pub(crate) mod test_strategies { }; } - #[derive(Clone, Copy)] + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct EmptyInMax; impl_usize_max_zst!(EmptyInMax); + #[derive(Clone, Copy)] pub struct HasDebug; impl Debug for HasDebug { @@ -160,15 +155,4 @@ pub(crate) mod test_strategies { } impl_usize_max_zst!(HasDebug); - - #[derive(Debug, Clone, PartialEq, Eq)] - pub struct PanicsInDrop; - - impl Drop for PanicsInDrop { - fn drop(&mut self) { - panic!("oh no!!!"); - } - } - - impl_usize_max_zst!(PanicsInDrop); }