diff --git a/crates/erasable/src/lib.rs b/crates/erasable/src/lib.rs index 8b15078..6fc5693 100644 --- a/crates/erasable/src/lib.rs +++ b/crates/erasable/src/lib.rs @@ -185,6 +185,60 @@ 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). diff --git a/crates/erasable/tests/smoke.rs b/crates/erasable/tests/smoke.rs index 6452a08..b0f3c59 100644 --- a/crates/erasable/tests/smoke.rs +++ b/crates/erasable/tests/smoke.rs @@ -32,3 +32,58 @@ 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 { + as ErasablePtr>::with(&erased, |bigbox| { + 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 { + as ErasablePtr>::with_mut(&mut erased, |bigbox| { + 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_ptr(); + unsafe { + as ErasablePtr>::with_mut(&mut erased, |bigbox| { + let mut newboxed: Box = Default::default(); + newboxed.0[0] = 123456; + *bigbox = newboxed; + assert_ne!(*bigbox, Default::default()); + }) + } + + let e2 = erased.as_ptr(); + assert_ne!(e1, e2); + + // drop it, otherwise we would leak memory here + unsafe { as ErasablePtr>::unerase(erased) }; +}