Skip to content

Commit

Permalink
Use NonNull as Box ptr to get correct variance and null ptr optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
faern committed Jun 20, 2020
1 parent f316f19 commit 2b108de
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 12 deletions.
29 changes: 17 additions & 12 deletions src/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{borrow, fmt, hash, mem, ptr};
/// is tightly integrated with the compiler and uses magic that normal crates can't.
/// This version instead provides [`Box::into_value`] which does the same thing.
pub struct Box<T: ?Sized> {
ptr: *mut T,
ptr: ptr::NonNull<T>,
}

impl<T> Box<T> {
Expand All @@ -21,7 +21,10 @@ impl<T> Box<T> {
let layout = alloc::Layout::new::<T>();
let ptr = unsafe { alloc::alloc(layout) } as *mut T;
unsafe { ptr::write(ptr, x) };
Self { ptr }
// SAFETY: `alloc::alloc` should never return a null pointer.
Self {
ptr: unsafe { ptr::NonNull::new_unchecked(ptr) },
}
}

/// Consumes the box and returns the value in it.
Expand All @@ -45,9 +48,9 @@ impl<T> Box<T> {
/// }
/// ```
pub fn into_value(self) -> T {
let value = unsafe { ptr::read(self.ptr) };
let value = unsafe { ptr::read(self.ptr.as_ptr()) };
let layout = alloc::Layout::new::<T>();
unsafe { alloc::dealloc(self.ptr as *mut u8, layout) };
unsafe { alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout) };
mem::forget(self);
value
}
Expand All @@ -67,7 +70,9 @@ impl<T: ?Sized> Box<T> {
/// a double-free may occur if the function is called twice on the same raw pointer.
#[inline]
pub const unsafe fn from_raw(ptr: *mut T) -> Box<T> {
Self { ptr }
Self {
ptr: ptr::NonNull::new_unchecked(ptr),
}
}

/// Consumes the Box, returning a wrapped raw pointer.
Expand All @@ -80,18 +85,18 @@ impl<T: ?Sized> Box<T> {
pub fn into_raw(b: Box<T>) -> *mut T {
let ptr = b.ptr;
mem::forget(b);
ptr
ptr.as_ptr()
}
}

impl<T: ?Sized> Drop for Box<T> {
fn drop(&mut self) {
unsafe {
let size = mem::size_of_val(&*self.ptr);
let align = mem::align_of_val(&*self.ptr);
let size = mem::size_of_val(self.ptr.as_ref());
let align = mem::align_of_val(self.ptr.as_ref());
let layout = alloc::Layout::from_size_align(size, align).unwrap();
ptr::drop_in_place(self.ptr);
alloc::dealloc(self.ptr as *mut u8, layout);
ptr::drop_in_place(self.ptr.as_ptr());
alloc::dealloc(self.ptr.as_ptr() as *mut u8, layout);
}
}
}
Expand All @@ -103,13 +108,13 @@ impl<T: ?Sized> std::ops::Deref for Box<T> {
type Target = T;

fn deref(&self) -> &T {
unsafe { &*self.ptr }
unsafe { self.ptr.as_ref() }
}
}

impl<T: ?Sized> std::ops::DerefMut for Box<T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr }
unsafe { self.ptr.as_mut() }
}
}

Expand Down
39 changes: 39 additions & 0 deletions tests/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,42 @@ fn allocate_and_leak() {
Box::into_raw(detect_drop);
});
}

#[test]
fn same_size_as_std_box() {
use std::boxed::Box as StdBox;

macro_rules! same_size_and_alignment {
($t:ty) => {
assert_eq!(
mem::size_of::<Box<$t>>(),
mem::size_of::<StdBox<$t>>(),
"size of Box<{}>",
stringify!($t),
);
assert_eq!(
mem::align_of::<Box<$t>>(),
mem::align_of::<StdBox<$t>>(),
"align of Box<{}>",
stringify!($t),
);
assert_eq!(
mem::size_of::<Option<Box<$t>>>(),
mem::size_of::<Option<StdBox<$t>>>(),
"size of Option<Box<{}>>",
stringify!($t),
);
assert_eq!(
mem::align_of::<Option<Box<$t>>>(),
mem::align_of::<Option<StdBox<$t>>>(),
"align of Option<Box<{}>>",
stringify!($t),
);
};
}

same_size_and_alignment!(std::convert::Infallible);
same_size_and_alignment!(());
same_size_and_alignment!(u8);
same_size_and_alignment!([u32; 1024]);
}

0 comments on commit 2b108de

Please sign in to comment.