Rollup merge of #110483 - tleibert:thin-box-try-new, r=dtolnay

Create try_new function for ThinBox

The `allocator_api` feature has proven very useful in my work in the FreeBSD kernel. I've found a few places where a `ThinBox` #92791 would be useful, but it must be able to be fallibly allocated for it to be used in the kernel.

This PR proposes a change to add such a constructor for ThinBox.

ACP: https://github.com/rust-lang/libs-team/issues/213
This commit is contained in:
Matthias Krüger 2024-02-11 23:19:07 +01:00 committed by GitHub
commit 251a09e151
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -67,6 +67,26 @@ pub fn new(value: T) -> Self {
let ptr = WithOpaqueHeader::new(meta, value); let ptr = WithOpaqueHeader::new(meta, value);
ThinBox { ptr, _marker: PhantomData } ThinBox { ptr, _marker: PhantomData }
} }
/// Moves a type to the heap with its [`Metadata`] stored in the heap allocation instead of on
/// the stack. Returns an error if allocation fails, instead of aborting.
///
/// # Examples
///
/// ```
/// #![feature(allocator_api)]
/// #![feature(thin_box)]
/// use std::boxed::ThinBox;
///
/// let five = ThinBox::try_new(5)?;
/// # Ok::<(), std::alloc::AllocError>(())
/// ```
///
/// [`Metadata`]: core::ptr::Pointee::Metadata
pub fn try_new(value: T) -> Result<Self, core::alloc::AllocError> {
let meta = ptr::metadata(&value);
WithOpaqueHeader::try_new(meta, value).map(|ptr| ThinBox { ptr, _marker: PhantomData })
}
} }
#[unstable(feature = "thin_box", issue = "92791")] #[unstable(feature = "thin_box", issue = "92791")]
@ -179,6 +199,10 @@ fn new<H, T>(header: H, value: T) -> Self {
let ptr = WithHeader::new(header, value); let ptr = WithHeader::new(header, value);
Self(ptr.0) Self(ptr.0)
} }
fn try_new<H, T>(header: H, value: T) -> Result<Self, core::alloc::AllocError> {
WithHeader::try_new(header, value).map(|ptr| Self(ptr.0))
}
} }
impl<H> WithHeader<H> { impl<H> WithHeader<H> {
@ -224,6 +248,46 @@ fn new<T>(header: H, value: T) -> WithHeader<H> {
} }
} }
/// Non-panicking version of `new`.
/// Any error is returned as `Err(core::alloc::AllocError)`.
fn try_new<T>(header: H, value: T) -> Result<WithHeader<H>, core::alloc::AllocError> {
let value_layout = Layout::new::<T>();
let Ok((layout, value_offset)) = Self::alloc_layout(value_layout) else {
return Err(core::alloc::AllocError);
};
unsafe {
// Note: It's UB to pass a layout with a zero size to `alloc::alloc`, so
// we use `layout.dangling()` for this case, which should have a valid
// alignment for both `T` and `H`.
let ptr = if layout.size() == 0 {
// Some paranoia checking, mostly so that the ThinBox tests are
// more able to catch issues.
debug_assert!(
value_offset == 0 && mem::size_of::<T>() == 0 && mem::size_of::<H>() == 0
);
layout.dangling()
} else {
let ptr = alloc::alloc(layout);
if ptr.is_null() {
return Err(core::alloc::AllocError);
}
// Safety:
// - The size is at least `aligned_header_size`.
let ptr = ptr.add(value_offset) as *mut _;
NonNull::new_unchecked(ptr)
};
let result = WithHeader(ptr, PhantomData);
ptr::write(result.header(), header);
ptr::write(result.value().cast(), value);
Ok(result)
}
}
// Safety: // Safety:
// - Assumes that either `value` can be dereferenced, or is the // - Assumes that either `value` can be dereferenced, or is the
// `NonNull::dangling()` we use when both `T` and `H` are ZSTs. // `NonNull::dangling()` we use when both `T` and `H` are ZSTs.