about summary refs log tree commit diff
diff options
context:
space:
mode:
authorDylan DPC <99973273+Dylan-DPC@users.noreply.github.com>2022-07-01 20:19:17 +0530
committerGitHub <noreply@github.com>2022-07-01 20:19:17 +0530
commit9dd328855736e1c69f3df696e6084bbb6749d0cf (patch)
tree9b0744a6a7769344d6bcad9f6ef41028b5cad4a4
parente2ed8d7ed167d7b7026cc9ea990e8fec2a1d5d46 (diff)
parente67e1655854b8f2c94dff80f4da2e595bd1ceaa8 (diff)
downloadrust-9dd328855736e1c69f3df696e6084bbb6749d0cf.tar.gz
rust-9dd328855736e1c69f3df696e6084bbb6749d0cf.zip
Rollup merge of #98585 - cuviper:covariant-thinbox, r=thomcc
Make `ThinBox<T>` covariant in `T`

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.

Fixes the problem noted in <https://github.com/rust-lang/rust/issues/92791#issuecomment-1104636249>.
-rw-r--r--library/alloc/src/boxed/thin.rs33
-rw-r--r--library/alloc/tests/thin_box.rs7
2 files changed, 34 insertions, 6 deletions
diff --git a/library/alloc/src/boxed/thin.rs b/library/alloc/src/boxed/thin.rs
index 09308d4d090..649ccfcaa9e 100644
--- a/library/alloc/src/boxed/thin.rs
+++ b/library/alloc/src/boxed/thin.rs
@@ -31,7 +31,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>,
 }
 
@@ -59,7 +61,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 }
     }
 }
@@ -83,7 +85,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 }
     }
 }
@@ -130,7 +132,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);
         }
     }
 }
@@ -140,11 +142,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<_>) }
     }
 }
 
@@ -153,8 +160,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> {
diff --git a/library/alloc/tests/thin_box.rs b/library/alloc/tests/thin_box.rs
index 70d1db8b457..368aa564f94 100644
--- a/library/alloc/tests/thin_box.rs
+++ b/library/alloc/tests/thin_box.rs
@@ -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