Skip to content

Commit

Permalink
implement ErasedPtr as newtype
Browse files Browse the repository at this point in the history
This changes ErasedPtr from type alias to a newtype implementation.

Thus ErasedPtr implements few methods now:

 * `as_unit_ptr(&self) -> *const ()` getting the pointer value for diagnostics.
 * `with(FnOnce(&E)-> T)` and `with_mut(FnOnce(&E)-> T)` are moved from the Eraseable trait to
   ErasedPtr.
  • Loading branch information
cehteh committed Feb 2, 2023
1 parent 5277f7c commit d3b40c7
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 63 deletions.
126 changes: 69 additions & 57 deletions crates/erasable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Erased>;
#[derive(Debug, Copy, Clone, PartialEq)]
#[repr(transparent)]
pub struct ErasedPtr(ptr::NonNull<Erased>);

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<T>` 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<i32> = 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<i32>| rc.clone())
/// };
///
/// assert_eq!(*cloned, 123);
/// # unsafe {<Rc<i32> 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<E, F, T>(&self, f: F) -> T
where
E: ErasablePtr,
F: FnOnce(&E) -> T,
{
f(&ManuallyDrop::new(<E as ErasablePtr>::unerase(*self)))
}

/// Run a closure on a mutable borrow of the real pointer. Unlike the `Thin<T>` 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<E, F, T>(&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(<E as ErasablePtr>::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;
Expand Down Expand Up @@ -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<T>` 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<i32> = Rc::new(123);
///
/// let erased: ErasedPtr = ErasablePtr::erase(rc);
///
/// let cloned = unsafe {
/// <Rc<i32> as ErasablePtr>::with(&erased, |rc| rc.clone())
/// };
///
/// assert_eq!(*cloned, 123);
/// # unsafe {<Rc<i32> 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<F, T>(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<T>` 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<F, T>(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).
Expand Down Expand Up @@ -327,7 +339,7 @@ pub unsafe trait Erasable {
/// Erase a pointer.
#[inline(always)]
pub fn erase<T: ?Sized>(ptr: ptr::NonNull<T>) -> 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.
Expand Down Expand Up @@ -670,7 +682,7 @@ where
unsafe impl<T: Sized> Erasable for T {
unsafe fn unerase(this: ErasedPtr) -> ptr::NonNull<T> {
// 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;
Expand Down
23 changes: 17 additions & 6 deletions crates/erasable/tests/smoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ fn erasing() {
let boxed: Box<Big> = 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<Big> = unsafe { ErasablePtr::unerase(erased) };
assert_eq!(&*boxed as *const _ as usize, ptr);
}
Expand All @@ -40,7 +40,17 @@ fn with_fn() {
let erased: ErasedPtr = ErasablePtr::erase(boxed);

unsafe {
<Box<Big> as ErasablePtr>::with(&erased, |bigbox| {
// clippy errs here:
// warning: you seem to be trying to use `&Box<T>`. Consider using just `&T`
// --> crates/erasable/tests/smoke.rs:45:30
// |
// 45 | erased.with(|bigbox: &Box<Big>| {
// | ^^^^^^^^^ help: try: `&Big`
//
// We really need to borrow a &Box<Big> in this case because that what we constructed
// the ErasedPtr from.
#[allow(clippy::borrowed_box)]
erased.with(|bigbox: &Box<Big>| {
assert_eq!(*bigbox, Default::default());
})
}
Expand All @@ -56,7 +66,7 @@ fn with_mut_fn() {
let mut erased: ErasedPtr = ErasablePtr::erase(boxed);

unsafe {
<Box<Big> as ErasablePtr>::with_mut(&mut erased, |bigbox| {
erased.with_mut(|bigbox: &mut Box<Big>| {
bigbox.0[0] = 123456;
assert_ne!(*bigbox, Default::default());
})
Expand All @@ -71,17 +81,18 @@ fn with_mut_fn_replacethis() {
let boxed: Box<Big> = Default::default();

let mut erased: ErasedPtr = ErasablePtr::erase(boxed);
let e1 = erased.as_ptr();
let e1 = erased.as_unit_ptr();

unsafe {
<Box<Big> as ErasablePtr>::with_mut(&mut erased, |bigbox| {
erased.with_mut(|bigbox: &mut Box<Big>| {
let mut newboxed: Box<Big> = Default::default();
newboxed.0[0] = 123456;
*bigbox = newboxed;
assert_ne!(*bigbox, Default::default());
})
}

let e2 = erased.as_ptr();
let e2 = erased.as_unit_ptr();
assert_ne!(e1, e2);

// drop it, otherwise we would leak memory here
Expand Down

0 comments on commit d3b40c7

Please sign in to comment.