Skip to content

Commit

Permalink
Make ThinBox<T> covariant in T
Browse files Browse the repository at this point in the history
Just like `Box<T>`, we want `ThinBox<T>` to be covariant in `T`, but the
projection in `WithHeader<<T as Pointee>::Metadata>` was making it
invariant. This is now hidden as `WithOpaqueHeader`, which we type-cast
whenever the real `WithHeader<H>` type is needed.
  • Loading branch information
cuviper committed Jun 27, 2022
1 parent bd2e51a commit e67e165
Show file tree
Hide file tree
Showing 2 changed files with 34 additions and 6 deletions.
33 changes: 27 additions & 6 deletions library/alloc/src/boxed/thin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ use core::ptr::{self, NonNull};
/// ```
#[unstable(feature = "thin_box", issue = "92791")]
pub struct ThinBox<T: ?Sized> {
ptr: WithHeader<<T as Pointee>::Metadata>,
// This is essentially `WithHeader<<T as Pointee>::Metadata>`,
// but that would be invariant in `T`, and we want covariance.
ptr: WithOpaqueHeader,
_marker: PhantomData<T>,
}

Expand All @@ -49,7 +51,7 @@ impl<T> ThinBox<T> {
#[cfg(not(no_global_oom_handling))]
pub fn new(value: T) -> Self {
let meta = ptr::metadata(&value);
let ptr = WithHeader::new(meta, value);
let ptr = WithOpaqueHeader::new(meta, value);
ThinBox { ptr, _marker: PhantomData }
}
}
Expand All @@ -73,7 +75,7 @@ impl<Dyn: ?Sized> ThinBox<Dyn> {
T: Unsize<Dyn>,
{
let meta = ptr::metadata(&value as &Dyn);
let ptr = WithHeader::new(meta, value);
let ptr = WithOpaqueHeader::new(meta, value);
ThinBox { ptr, _marker: PhantomData }
}
}
Expand Down Expand Up @@ -120,7 +122,7 @@ impl<T: ?Sized> Drop for ThinBox<T> {
unsafe {
let value = self.deref_mut();
let value = value as *mut T;
self.ptr.drop::<T>(value);
self.with_header().drop::<T>(value);
}
}
}
Expand All @@ -130,11 +132,16 @@ impl<T: ?Sized> ThinBox<T> {
fn meta(&self) -> <T as Pointee>::Metadata {
// Safety:
// - NonNull and valid.
unsafe { *self.ptr.header() }
unsafe { *self.with_header().header() }
}

fn data(&self) -> *mut u8 {
self.ptr.value()
self.with_header().value()
}

fn with_header(&self) -> &WithHeader<<T as Pointee>::Metadata> {
// SAFETY: both types are transparent to `NonNull<u8>`
unsafe { &*((&self.ptr) as *const WithOpaqueHeader as *const WithHeader<_>) }
}
}

Expand All @@ -143,8 +150,22 @@ impl<T: ?Sized> ThinBox<T> {
/// metadata (`H`) are ZSTs.
/// 2. A pointer to a valid `T` that has a header `H` directly before the
/// pointed-to location.
#[repr(transparent)]
struct WithHeader<H>(NonNull<u8>, PhantomData<H>);

/// An opaque representation of `WithHeader<H>` to avoid the
/// projection invariance of `<T as Pointee>::Metadata`.
#[repr(transparent)]
struct WithOpaqueHeader(NonNull<u8>);

impl WithOpaqueHeader {
#[cfg(not(no_global_oom_handling))]
fn new<H, T>(header: H, value: T) -> Self {
let ptr = WithHeader::new(header, value);
Self(ptr.0)
}
}

impl<H> WithHeader<H> {
#[cfg(not(no_global_oom_handling))]
fn new<T>(header: H, value: T) -> WithHeader<H> {
Expand Down
7 changes: 7 additions & 0 deletions library/alloc/tests/thin_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ fn want_thin() {
assert!(is_thin::<i32>());
}

#[allow(dead_code)]
fn assert_covariance() {
fn thin_box<'new>(b: ThinBox<[&'static str]>) -> ThinBox<[&'new str]> {
b
}
}

#[track_caller]
fn verify_aligned<T>(ptr: *const T) {
// Use `black_box` to attempt to obscure the fact that we're calling this
Expand Down

0 comments on commit e67e165

Please sign in to comment.