Skip to content

alloc: add some try_* methods Rust-for-Linux needs #91559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions library/alloc/src/collections/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ pub use linked_list::LinkedList;
#[doc(no_inline)]
pub use vec_deque::VecDeque;

#[cfg(not(no_global_oom_handling))]
use crate::alloc::handle_alloc_error;
use crate::alloc::{Layout, LayoutError};
#[cfg(not(no_global_oom_handling))]
use crate::raw_vec::capacity_overflow;
use core::fmt::Display;

/// The error type for `try_reserve` methods.
Expand Down Expand Up @@ -106,6 +110,19 @@ pub enum TryReserveErrorKind {
},
}

#[cfg(not(no_global_oom_handling))]
impl TryReserveError {
/// Panic, or worse, according to the global handlers for each case.
pub(crate) fn handle(self) -> ! {
match self {
TryReserveErrorKind::CapacityOverflow => capacity_overflow(),
TryReserveErrorKind::AllocError { layout, non_exhaustive: () } => {
handle_alloc_error(layout)
}
}
}
}

#[unstable(
feature = "try_reserve_kind",
reason = "Uncertain how much info should be exposed",
Expand Down
1 change: 1 addition & 0 deletions library/alloc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
#![feature(iter_advance_by)]
#![feature(iter_zip)]
#![feature(layout_for_ptr)]
#![feature(more_fallible_allocation_methods)]
#![feature(maybe_uninit_extra)]
#![feature(maybe_uninit_slice)]
#![cfg_attr(test, feature(new_uninit))]
Expand Down
104 changes: 91 additions & 13 deletions library/alloc/src/raw_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,42 @@ impl<T> RawVec<T, Global> {
Self::with_capacity_in(capacity, Global)
}

/// Tries to create a `RawVec` (on the system heap) with exactly the
/// capacity and alignment requirements for a `[T; capacity]`. This is
/// equivalent to calling `RawVec::new` when `capacity` is `0` or `T` is
/// zero-sized. Note that if `T` is zero-sized this means you will
/// *not* get a `RawVec` with the requested capacity.
#[inline]
pub fn try_with_capacity(capacity: usize) -> Result<Self, TryReserveError> {
Self::try_with_capacity_in(capacity, Global)
}

/// Like `with_capacity`, but guarantees the buffer is zeroed.
#[cfg(not(any(no_global_oom_handling, test)))]
#[must_use]
#[inline]
pub fn with_capacity_zeroed(capacity: usize) -> Self {
Self::with_capacity_zeroed_in(capacity, Global)
}

/// Like `try_with_capacity`, but guarantees a successfully allocated buffer is zeroed.
#[inline]
pub fn try_with_capacity_zeroed(capacity: usize) -> Result<Self, TryReserveError> {
Self::try_with_capacity_zeroed_in(capacity, Global)
}

/// Reconstitutes a `RawVec` from a pointer and capacity.
///
/// # Safety
///
/// The `ptr` must be allocated (on the system heap), and with the given `capacity`.
/// The `capacity` cannot exceed `isize::MAX` for sized types. (only a concern on 32-bit
/// systems). ZST vectors may have a capacity up to `usize::MAX`.
/// If the `ptr` and `capacity` come from a `RawVec`, then this is guaranteed.
#[inline]
pub unsafe fn from_raw_parts(ptr: *mut T, capacity: usize) -> Self {
unsafe { Self::from_raw_parts_in(ptr, capacity, Global) }
}
}

impl<T, A: Allocator> RawVec<T, A> {
Expand Down Expand Up @@ -132,6 +161,13 @@ impl<T, A: Allocator> RawVec<T, A> {
Self::allocate_in(capacity, AllocInit::Uninitialized, alloc)
}

/// Like `try_with_capacity`, but parameterized over the choice of
/// allocator for the returned `RawVec`.
#[inline]
pub fn try_with_capacity_in(capacity: usize, alloc: A) -> Result<Self, TryReserveError> {
Self::try_allocate_in(capacity, AllocInit::Uninitialized, alloc)
}

/// Like `with_capacity_zeroed`, but parameterized over the choice
/// of allocator for the returned `RawVec`.
#[cfg(not(no_global_oom_handling))]
Expand All @@ -140,6 +176,21 @@ impl<T, A: Allocator> RawVec<T, A> {
Self::allocate_in(capacity, AllocInit::Zeroed, alloc)
}

/// Like `with_capacity_zeroed`, but parameterized over the choice
/// of allocator for the returned `RawVec`.
#[inline]
pub fn try_with_capacity_zeroed_in(capacity: usize, alloc: A) -> Result<Self, TryReserveError> {
Self::try_allocate_in(capacity, AllocInit::Zeroed, alloc)
}

/// Converts a `Box<[T]>` into a `RawVec<T>`.
pub fn from_box(slice: Box<[T], A>) -> Self {
unsafe {
let (slice, alloc) = Box::into_raw_with_allocator(slice);
RawVec::from_raw_parts_in(slice.as_mut_ptr(), slice.len(), alloc)
}
}

/// Converts the entire buffer into `Box<[MaybeUninit<T>]>` with the specified `len`.
///
/// Note that this will correctly reconstitute any `cap` changes
Expand Down Expand Up @@ -168,33 +219,38 @@ impl<T, A: Allocator> RawVec<T, A> {

#[cfg(not(no_global_oom_handling))]
fn allocate_in(capacity: usize, init: AllocInit, alloc: A) -> Self {
match Self::try_allocate_in(capacity, init, alloc) {
Ok(r) => r,
Err(e) => e.handle(),
}
}

fn try_allocate_in(
capacity: usize,
init: AllocInit,
alloc: A,
) -> Result<Self, TryReserveError> {
if mem::size_of::<T>() == 0 {
Self::new_in(alloc)
Ok(Self::new_in(alloc))
} else {
// We avoid `unwrap_or_else` here because it bloats the amount of
// LLVM IR generated.
let layout = match Layout::array::<T>(capacity) {
Ok(layout) => layout,
Err(_) => capacity_overflow(),
};
match alloc_guard(layout.size()) {
Ok(_) => {}
Err(_) => capacity_overflow(),
}
let layout = match Layout::array::<T>(capacity)?;
match alloc_guard(layout.size())?;
let result = match init {
AllocInit::Uninitialized => alloc.allocate(layout),
AllocInit::Zeroed => alloc.allocate_zeroed(layout),
};
let ptr = match result {
Ok(ptr) => ptr,
Err(_) => handle_alloc_error(layout),
Err(_) => return Err(TryReserveErrorKind::AllocError { layout, non_exhaustive: () }),
};

Self {
Ok(Self {
ptr: unsafe { Unique::new_unchecked(ptr.cast().as_ptr()) },
cap: Self::capacity_from_bytes(ptr.len()),
alloc,
}
})
}
}

Expand Down Expand Up @@ -349,7 +405,29 @@ impl<T, A: Allocator> RawVec<T, A> {
/// Aborts on OOM.
#[cfg(not(no_global_oom_handling))]
pub fn shrink_to_fit(&mut self, amount: usize) {
handle_reserve(self.shrink(amount));
handle_reserve(self.try_shrink_to_fit(amount));
}

/// Tries to shrink the allocation down to the specified amount. If the given amount
/// is 0, actually completely deallocates.
///
/// # Panics
///
/// Panics if the given amount is *larger* than the current capacity.
pub fn try_shrink_to_fit(&mut self, amount: usize) -> Result<(), TryReserveError> {
assert!(amount <= self.capacity(), "Tried to shrink to a larger capacity");

let (ptr, layout) = if let Some(mem) = self.current_memory() { mem } else { return Ok(()) };
let new_size = amount * mem::size_of::<T>();

let ptr = unsafe {
let new_layout = Layout::from_size_align_unchecked(new_size, layout.align());
self.alloc
.shrink(ptr, layout, new_layout)
.map_err(|_| AllocError { layout: new_layout, non_exhaustive: () })?
};
self.set_ptr(ptr);
Ok(())
}
}

Expand Down
97 changes: 96 additions & 1 deletion library/alloc/src/slice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,11 @@ use core::mem::size_of;
use core::ptr;

use crate::alloc::Allocator;
#[cfg(not(no_global_oom_handling))]
use crate::alloc::Global;
#[cfg(not(no_global_oom_handling))]
use crate::borrow::ToOwned;
use crate::boxed::Box;
use crate::collections::TryReserveError;
use crate::vec::Vec;

#[unstable(feature = "slice_range", issue = "76393")]
Expand Down Expand Up @@ -157,6 +157,7 @@ mod hack {
use core::alloc::Allocator;

use crate::boxed::Box;
use crate::collections::TryReserveError;
use crate::vec::Vec;

// We shouldn't add inline attribute to this since this is used in
Expand All @@ -176,13 +177,27 @@ mod hack {
T::to_vec(s, alloc)
}

#[inline]
pub fn try_to_vec<T: TryConvertVec, A: Allocator>(
s: &[T],
alloc: A,
) -> Result<Vec<T, A>, TryReserveError> {
T::try_to_vec(s, alloc)
}

#[cfg(not(no_global_oom_handling))]
pub trait ConvertVec {
fn to_vec<A: Allocator>(s: &[Self], alloc: A) -> Vec<Self, A>
where
Self: Sized;
}

pub trait TryConvertVec {
fn try_to_vec<A: Allocator>(s: &[Self], alloc: A) -> Result<Vec<Self, A>, TryReserveError>
where
Self: Sized;
}

#[cfg(not(no_global_oom_handling))]
impl<T: Clone> ConvertVec for T {
#[inline]
Expand Down Expand Up @@ -235,6 +250,45 @@ mod hack {
v
}
}

impl<T: Clone> TryConvertVec for T {
#[inline]
default fn try_to_vec<A: Allocator>(
s: &[Self],
alloc: A,
) -> Result<Vec<Self, A>, TryReserveError> {
struct DropGuard<'a, T, A: Allocator> {
vec: &'a mut Vec<T, A>,
num_init: usize,
}
impl<'a, T, A: Allocator> Drop for DropGuard<'a, T, A> {
#[inline]
fn drop(&mut self) {
// SAFETY:
// items were marked initialized in the loop below
unsafe {
self.vec.set_len(self.num_init);
}
}
}
let mut vec = Vec::try_with_capacity_in(s.len(), alloc)?;
let mut guard = DropGuard { vec: &mut vec, num_init: 0 };
let slots = guard.vec.spare_capacity_mut();
// .take(slots.len()) is necessary for LLVM to remove bounds checks
// and has better codegen than zip.
for (i, b) in s.iter().enumerate().take(slots.len()) {
guard.num_init = i;
slots[i].write(b.clone());
}
core::mem::forget(guard);
// SAFETY:
// the vec was allocated and initialized above to at least this length.
unsafe {
vec.set_len(s.len());
}
Ok(vec)
}
}
}

#[lang = "slice_alloc"]
Expand Down Expand Up @@ -474,6 +528,24 @@ impl<T> [T] {
self.to_vec_in(Global)
}

/// Tries to copy `self` into a new `Vec`.
///
/// # Examples
///
/// ```
/// let s = [10, 40, 30];
/// let x = s.try_to_vec().unwrap();
/// // Here, `s` and `x` can be modified independently.
/// ```
#[inline]
#[unstable(feature = "more_fallible_allocation_methods", issue = "86942")]
pub fn try_to_vec(&self) -> Result<Vec<T>, TryReserveError>
where
T: Clone,
{
self.try_to_vec_in(Global)
}

/// Copies `self` into a new `Vec` with an allocator.
///
/// # Examples
Expand All @@ -498,6 +570,29 @@ impl<T> [T] {
hack::to_vec(self, alloc)
}

/// Tries to copy `self` into a new `Vec` with an allocator.
///
/// # Examples
///
/// ```
/// #![feature(allocator_api)]
///
/// use std::alloc::System;
///
/// let s = [10, 40, 30];
/// let x = s.try_to_vec_in(System).unwrap();
/// // Here, `s` and `x` can be modified independently.
/// ```
#[inline]
#[unstable(feature = "more_fallible_allocation_methods", issue = "86942")]
pub fn try_to_vec_in<A: Allocator>(&self, alloc: A) -> Result<Vec<T, A>, TryReserveError>
where
T: Clone,
{
// N.B., see the `hack` module in this file for more details.
hack::try_to_vec(self, alloc)
}

/// Converts `self` into a vector without clones or allocation.
///
/// The resulting vector can be converted back into a box via
Expand Down
17 changes: 17 additions & 0 deletions library/alloc/src/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use core::unicode::conversions;

use crate::borrow::ToOwned;
use crate::boxed::Box;
use crate::collections::TryReserveError;
use crate::slice::{Concat, Join, SliceIndex};
use crate::string::String;
use crate::vec::Vec;
Expand Down Expand Up @@ -582,6 +583,22 @@ impl str {
// make_ascii_lowercase() preserves the UTF-8 invariant.
unsafe { String::from_utf8_unchecked(bytes) }
}

/// Tries to create a `String`.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// let s: &str = "a";
/// let ss: String = s.try_to_owned().unwrap();
/// ```
#[inline]
#[unstable(feature = "more_fallible_allocation_methods", issue = "86942")]
pub fn try_to_owned(&self) -> Result<String, TryReserveError> {
unsafe { Ok(String::from_utf8_unchecked(self.as_bytes().try_to_vec()?)) }
}
}

/// Converts a boxed slice of bytes to a boxed string slice without checking
Expand Down
4 changes: 2 additions & 2 deletions library/alloc/src/string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@
use core::char::{decode_utf16, REPLACEMENT_CHARACTER};
use core::fmt;
use core::hash;
use core::iter::FusedIterator;
#[cfg(not(no_global_oom_handling))]
use core::iter::FromIterator;
use core::iter::{from_fn, FusedIterator};
use core::iter::{from_fn, FromIterator};
#[cfg(not(no_global_oom_handling))]
use core::ops::Add;
#[cfg(not(no_global_oom_handling))]
Expand Down
Loading