diff --git a/crates/erasable/src/lib.rs b/crates/erasable/src/lib.rs index 8b15078..dea1b86 100644 --- a/crates/erasable/src/lib.rs +++ b/crates/erasable/src/lib.rs @@ -47,7 +47,75 @@ use core::{ /// The current implementation uses a `struct Erased` with size 0 and align 1. /// If you want to offset the pointer, make sure to cast to a `u8` or other known type pointer first. /// When `Erased` becomes an extern type, it will properly have unknown size and align. -pub type ErasedPtr = ptr::NonNull; +#[derive(Debug, Copy, Clone, PartialEq)] +#[repr(transparent)] +pub struct ErasedPtr(ptr::NonNull); + +impl ErasedPtr { + /// Gets the raw pointer as '*const ()' unit type. This keeps the internal representation + /// hidden and is not very useful but for diagnostics like logging memory addresses and + /// comparing pointers for partial equality. + #[inline(always)] + pub fn as_unit_ptr(&self) -> *const () { + self.0.as_ptr() as *const _ + } + + /// Run a closure on a borrow of the real pointer. Unlike the `Thin` wrapper this does + /// not carry the original type around. Thus it is required to specify the original impl + /// type as closure parameter. + /// + /// ``` + /// # use {erasable::*, std::rc::Rc}; + /// let rc: Rc = Rc::new(123); + /// + /// let erased: ErasedPtr = ErasablePtr::erase(rc); + /// + /// let cloned = unsafe { + /// // must specify a reference to the original type here + /// erased.with(|rc: &Rc| rc.clone()) + /// }; + /// + /// assert_eq!(*cloned, 123); + /// # unsafe { as ErasablePtr>::unerase(erased)}; // drop it + /// ``` + /// + /// # Safety + /// + /// * The erased pointer must have been created by `erase`. + /// * The specified impl type must be the original type. + #[inline(always)] + pub unsafe fn with(&self, f: F) -> T + where + E: ErasablePtr, + F: FnOnce(&E) -> T, + { + f(&ManuallyDrop::new(::unerase(*self))) + } + + /// Run a closure on a mutable borrow of the real pointer. Unlike the `Thin` wrapper + /// this does not carry the original type around. Thus it is required to specify the + /// original impl type as closure parameter. + /// + /// # Safety + /// + /// * The erased pointer must have been created by `erase`. + /// * The specified impl type must be the original type. + pub unsafe fn with_mut(&mut self, f: F) -> T + where + E: ErasablePtr, + F: FnOnce(&mut E) -> T, + { + // SAFETY: guard is required to write potentially changed pointer value, even on unwind + let mut this = scopeguard::guard( + ManuallyDrop::new(::unerase(*self)), + |unerased| { + ptr::write(self, ErasablePtr::erase(ManuallyDrop::into_inner(unerased))); + }, + ); + + f(&mut this) + } +} #[cfg(not(has_extern_type))] pub(crate) use priv_in_pub::Erased; @@ -273,7 +341,7 @@ pub unsafe trait Erasable { /// Erase a pointer. #[inline(always)] pub fn erase(ptr: ptr::NonNull) -> ErasedPtr { - unsafe { ptr::NonNull::new_unchecked(ptr.as_ptr() as *mut Erased) } + unsafe { ErasedPtr(ptr::NonNull::new_unchecked(ptr.as_ptr() as *mut Erased)) } } /// Wrapper struct to create thin pointer types. @@ -314,10 +382,6 @@ impl From

for Thin

{ } impl Thin

{ - fn inner(this: &Self) -> ManuallyDrop

{ - unsafe { ManuallyDrop::new(P::unerase(this.ptr)) } - } - // noinspection RsSelfConvention // `From` can't be impl'd because it's an impl on an uncovered type // `Into` can't be impl'd because it conflicts with the reflexive impl @@ -331,7 +395,8 @@ impl Thin

{ where F: FnOnce(&P) -> T, { - f(&Thin::inner(this)) + // SAFETY: The type &P is always correct here + unsafe { this.ptr.with(f) } } /// Run a closure with a mutable borrow of the real pointer. @@ -339,13 +404,8 @@ impl Thin

{ where F: FnOnce(&mut P) -> T, { - // SAFETY: guard is required to write potentially changed pointer value, even on unwind - let mut this = unsafe { - scopeguard::guard(P::unerase(this.ptr), |unerased| { - ptr::write(this, Thin::from(unerased)) - }) - }; - f(&mut this) + // SAFETY: The type &P is always correct here + unsafe { this.ptr.with_mut(f) } } /// Check two thin pointers for pointer equivalence. @@ -616,7 +676,7 @@ where unsafe impl Erasable for T { unsafe fn unerase(this: ErasedPtr) -> ptr::NonNull { // SAFETY: must not read the pointer for the safety of the impl directly below. - this.cast() + this.0.cast() } const ACK_1_1_0: bool = true; diff --git a/crates/erasable/tests/smoke.rs b/crates/erasable/tests/smoke.rs index 6452a08..4c6c728 100644 --- a/crates/erasable/tests/smoke.rs +++ b/crates/erasable/tests/smoke.rs @@ -13,7 +13,7 @@ fn erasing() { let boxed: Box = Box::new(Big::default()); let ptr = &*boxed as *const _ as usize; let erased: ErasedPtr = ErasablePtr::erase(boxed); - assert_eq!(erased.as_ptr() as usize, ptr); + assert_eq!(erased.as_unit_ptr() as usize, ptr); let boxed: Box = unsafe { ErasablePtr::unerase(erased) }; assert_eq!(&*boxed as *const _ as usize, ptr); } @@ -32,3 +32,69 @@ fn thinning() { Thin::with_mut(&mut thin, |thin| *thin = Default::default()); let boxed = Thin::into_inner(thin); } + +#[test] +fn with_fn() { + let boxed: Box = Default::default(); + + let erased: ErasedPtr = ErasablePtr::erase(boxed); + + unsafe { + // clippy errs here: + // warning: you seem to be trying to use `&Box`. Consider using just `&T` + // --> crates/erasable/tests/smoke.rs:45:30 + // | + // 45 | erased.with(|bigbox: &Box| { + // | ^^^^^^^^^ help: try: `&Big` + // + // We really need to borrow a &Box in this case because that what we constructed + // the ErasedPtr from. + #[allow(clippy::borrowed_box)] + erased.with(|bigbox: &Box| { + assert_eq!(*bigbox, Default::default()); + }) + } + + // drop it, otherwise we would leak memory here + unsafe { as ErasablePtr>::unerase(erased) }; +} + +#[test] +fn with_mut_fn() { + let boxed: Box = Default::default(); + + let mut erased: ErasedPtr = ErasablePtr::erase(boxed); + + unsafe { + erased.with_mut(|bigbox: &mut Box| { + bigbox.0[0] = 123456; + assert_ne!(*bigbox, Default::default()); + }) + } + + // drop it, otherwise we would leak memory here + unsafe { as ErasablePtr>::unerase(erased) }; +} + +#[test] +fn with_mut_fn_replacethis() { + let boxed: Box = Default::default(); + + let mut erased: ErasedPtr = ErasablePtr::erase(boxed); + let e1 = erased.as_unit_ptr(); + + unsafe { + erased.with_mut(|bigbox: &mut Box| { + let mut newboxed: Box = Default::default(); + newboxed.0[0] = 123456; + *bigbox = newboxed; + assert_ne!(*bigbox, Default::default()); + }) + } + + let e2 = erased.as_unit_ptr(); + assert_ne!(e1, e2); + + // drop it, otherwise we would leak memory here + unsafe { as ErasablePtr>::unerase(erased) }; +}