Skip to content

RFC: Feature/newtype #83

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 75 additions & 15 deletions crates/erasable/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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.
#[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<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.
#[inline(always)]
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 @@ -273,7 +341,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 @@ -314,10 +382,6 @@ impl<P: ErasablePtr> From<P> for Thin<P> {
}

impl<P: ErasablePtr> Thin<P> {
fn inner(this: &Self) -> ManuallyDrop<P> {
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
Expand All @@ -331,21 +395,17 @@ impl<P: ErasablePtr> Thin<P> {
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.
pub fn with_mut<F, T>(this: &mut Self, f: F) -> T
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.
Expand Down Expand Up @@ -616,7 +676,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
68 changes: 67 additions & 1 deletion 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 @@ -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<Big> = Default::default();

let erased: ErasedPtr = ErasablePtr::erase(boxed);

unsafe {
// 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());
})
}

// drop it, otherwise we would leak memory here
unsafe { <Box<Big> as ErasablePtr>::unerase(erased) };
}

#[test]
fn with_mut_fn() {
let boxed: Box<Big> = Default::default();

let mut erased: ErasedPtr = ErasablePtr::erase(boxed);

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

// drop it, otherwise we would leak memory here
unsafe { <Box<Big> as ErasablePtr>::unerase(erased) };
}

#[test]
fn with_mut_fn_replacethis() {
let boxed: Box<Big> = Default::default();

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

unsafe {
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_unit_ptr();
assert_ne!(e1, e2);

// drop it, otherwise we would leak memory here
unsafe { <Box<Big> as ErasablePtr>::unerase(erased) };
}