diff --git a/crates/erasable/src/lib.rs b/crates/erasable/src/lib.rs index 6fc5693..10e2cb7 100644 --- a/crates/erasable/src/lib.rs +++ b/crates/erasable/src/lib.rs @@ -47,7 +47,73 @@ 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. + 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. + 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; @@ -185,60 +251,6 @@ pub unsafe trait ErasablePtr { /// /// The erased pointer must have been created by `erase`. unsafe fn unerase(this: ErasedPtr) -> Self; - - /// 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 when calling this function. - /// - /// ``` - /// # use {erasable::*, std::rc::Rc}; - /// let rc: Rc = Rc::new(123); - /// - /// let erased: ErasedPtr = ErasablePtr::erase(rc); - /// - /// let cloned = unsafe { - /// as ErasablePtr>::with(&erased, |rc| rc.clone()) - /// }; - /// - /// assert_eq!(*cloned, 123); - /// # unsafe { as ErasablePtr>::unerase(erased)}; // drop it - /// ``` - /// - /// The main purpose of this function is to be able implement recursive types that would - /// be otherwise not representable in rust. - /// - /// # Safety - /// - /// * The erased pointer must have been created by `erase`. - /// * The specified impl type must be the original type. - unsafe fn with(this: &ErasedPtr, f: F) -> T - where - Self: Sized, - F: FnOnce(&Self) -> T, - { - f(&ManuallyDrop::new(Self::unerase(*this))) - } - - /// 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 when calling this function. - /// - /// # Safety - /// - /// * The erased pointer must have been created by `erase`. - /// * The specified impl type must be the original type. - unsafe fn with_mut(this: &mut ErasedPtr, f: F) -> T - where - Self: Sized, - F: FnOnce(&mut Self) -> T, - { - // SAFETY: guard is required to write potentially changed pointer value, even on unwind - let mut that = scopeguard::guard(ManuallyDrop::new(Self::unerase(*this)), |unerased| { - ptr::write(this, ErasablePtr::erase(ManuallyDrop::into_inner(unerased))); - }); - - f(&mut that) - } } /// A pointee type that supports type-erased pointers (thin pointers). @@ -327,7 +339,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. @@ -670,7 +682,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 b0f3c59..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); } @@ -40,7 +40,17 @@ fn with_fn() { let erased: ErasedPtr = ErasablePtr::erase(boxed); unsafe { - as ErasablePtr>::with(&erased, |bigbox| { + // 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()); }) } @@ -56,7 +66,7 @@ fn with_mut_fn() { let mut erased: ErasedPtr = ErasablePtr::erase(boxed); unsafe { - as ErasablePtr>::with_mut(&mut erased, |bigbox| { + erased.with_mut(|bigbox: &mut Box| { bigbox.0[0] = 123456; assert_ne!(*bigbox, Default::default()); }) @@ -71,9 +81,10 @@ fn with_mut_fn_replacethis() { let boxed: Box = Default::default(); let mut erased: ErasedPtr = ErasablePtr::erase(boxed); - let e1 = erased.as_ptr(); + let e1 = erased.as_unit_ptr(); + unsafe { - as ErasablePtr>::with_mut(&mut erased, |bigbox| { + erased.with_mut(|bigbox: &mut Box| { let mut newboxed: Box = Default::default(); newboxed.0[0] = 123456; *bigbox = newboxed; @@ -81,7 +92,7 @@ fn with_mut_fn_replacethis() { }) } - let e2 = erased.as_ptr(); + let e2 = erased.as_unit_ptr(); assert_ne!(e1, e2); // drop it, otherwise we would leak memory here