diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index b82cfe862904e..2d4f13167fd60 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -79,6 +79,7 @@ #![feature(slice_group_by)] #![feature(trusted_random_access)] #![deny(unsafe_op_in_unsafe_fn)] +#![cfg_attr(not(bootstrap), feature(unsize))] extern crate test; diff --git a/library/core/tests/ptr.rs b/library/core/tests/ptr.rs index 1cdcb8c97670d..224a58e3ccdb7 100644 --- a/library/core/tests/ptr.rs +++ b/library/core/tests/ptr.rs @@ -555,3 +555,115 @@ fn from_raw_parts() { assert_eq!(ptr::from_raw_parts_mut(address, 5), slice_ptr.as_ptr()); assert_eq!(NonNull::from_raw_parts(NonNull::new(address).unwrap(), 5), slice_ptr); } + +#[test] +#[cfg(not(bootstrap))] +fn thin_box() { + let foo = ThinBox::::new(4); + assert_eq!(foo.to_string(), "4"); + drop(foo); + let bar = ThinBox::::new(7); + assert_eq!(bar.to_string(), "7"); + + // A slightly more interesting library that could be built on top of metadata APIs. + // + // * It could be generalized to any `T: ?Sized` (not just trait object) + // if `{size,align}_of_for_meta(T::Metadata)` are added. + // * Constructing a `ThinBox` without consuming and deallocating a `Box` + // requires either the unstable `Unsize` marker trait, + // or the unstable `unsized_locals` language feature, + // or taking `&dyn T` and restricting to `T: Copy`. + + use std::alloc::*; + use std::marker::PhantomData; + + struct ThinBox + where + T: ?Sized + Pointee>, + { + ptr: NonNull>, + phantom: PhantomData, + } + + impl ThinBox + where + T: ?Sized + Pointee>, + { + pub fn new>(value: Value) -> Self { + let unsized_: &T = &value; + let meta = metadata(unsized_); + let meta_layout = Layout::for_value(&meta); + let value_layout = Layout::for_value(&value); + let (layout, offset) = meta_layout.extend(value_layout).unwrap(); + // `DynMetadata` is pointer-sized: + assert!(layout.size() > 0); + // If `ThinBox` is generalized to any `T: ?Sized`, + // handle ZSTs with a dangling pointer without going through `alloc()`, + // like `Box` does. + unsafe { + let ptr = NonNull::new(alloc(layout)) + .unwrap_or_else(|| handle_alloc_error(layout)) + .cast::>(); + ptr.as_ptr().write(meta); + ptr.cast::().as_ptr().add(offset).cast::().write(value); + Self { ptr, phantom: PhantomData } + } + } + + fn meta(&self) -> DynMetadata { + unsafe { *self.ptr.as_ref() } + } + + fn layout(&self) -> (Layout, usize) { + let meta = self.meta(); + Layout::for_value(&meta).extend(meta.layout()).unwrap() + } + + fn value_ptr(&self) -> *const T { + let (_, offset) = self.layout(); + let data_ptr = unsafe { self.ptr.cast::().as_ptr().add(offset) }; + ptr::from_raw_parts(data_ptr.cast(), self.meta()) + } + + fn value_mut_ptr(&mut self) -> *mut T { + let (_, offset) = self.layout(); + // FIXME: can this line be shared with the same in `value_ptr()` + // without upsetting Stacked Borrows? + let data_ptr = unsafe { self.ptr.cast::().as_ptr().add(offset) }; + from_raw_parts_mut(data_ptr.cast(), self.meta()) + } + } + + impl std::ops::Deref for ThinBox + where + T: ?Sized + Pointee>, + { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.value_ptr() } + } + } + + impl std::ops::DerefMut for ThinBox + where + T: ?Sized + Pointee>, + { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.value_mut_ptr() } + } + } + + impl std::ops::Drop for ThinBox + where + T: ?Sized + Pointee>, + { + fn drop(&mut self) { + let (layout, _) = self.layout(); + unsafe { + drop_in_place::(&mut **self); + dealloc(self.ptr.cast().as_ptr(), layout); + } + } + } +}