Skip to content

Commit

Permalink
vec: add try_* methods and a try_vec! macro to make Vec usable in wit…
Browse files Browse the repository at this point in the history
…hout infallible allocation methods

As a part of the work for rust-lang#86942 the `no_global_oom_handling` cfg was added in rust-lang#84266, however enabling that cfg makes it difficult to use `Vec`: the `vec!` macro is missing and there is no way to insert or push new elements, nor to create a `Vec` from an iterator (without resorting to `unsafe` APIs to manually expand and write to the underlying buffer).

This change adds `try_` equivalent methods for all methods in `Vec` that are disabled by `no_global_oom_handling` as well as a `try_vec!` as the equivalent of `vec!`.

Performance and implementation notes: A goal of this change was to make sure to NOT regress the performance of the existing infallible methods - a naive approach would be to move the actual implementation into the fallible method, then call it from the infallible method and `unwrap()`, but this would add extra compare+branch instructions into the infallible methods. It would also be possible to simply duplicate the code for the fallible version - I did opt for this in a few cases, but where the code was larger and more complex this would lead to a maintenance nightmare. Instead, I moved the implementation into an `*_impl` method that was then specialized based on a new `VecError` trait and returned `Result<_, VecError>`, this trait also provided a pair of methods for raising errors. Never (`!`) was used as the infallible version of this trait (and always panics) and `TryReserveError` as the fallible version (and returns the correct error object). All these `VecError` method were marked with `#[inline]`, so after inlining the compiler could see for the infallible version always returns `Ok()` on success (and panics on error) thus the `?` operator or `unwrap()` call was a no-op, and so the same non-branching instructions as before are generated.

I also added `try_from_iter` and `try_expand` methods for completeness, even though their infallible equivalents are trait implementations.
  • Loading branch information
dpaoliello committed Oct 10, 2022
1 parent 0265a3e commit 90cf90b
Show file tree
Hide file tree
Showing 12 changed files with 917 additions and 185 deletions.
7 changes: 7 additions & 0 deletions library/alloc/src/collections/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ impl TryReserveError {
pub fn kind(&self) -> TryReserveErrorKind {
self.kind.clone()
}

/// Create a new [`TryReserveError`] indicating that there was an allocation failure.
#[must_use]
#[unstable(feature = "more_fallible_allocation_methods", issue = "86942")]
pub fn alloc_error(layout: Layout) -> Self {
Self { kind: TryReserveErrorKind::AllocError { layout, non_exhaustive: () } }
}
}

/// Details of the allocation that caused a `TryReserveError`
Expand Down
85 changes: 85 additions & 0 deletions library/alloc/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,70 @@ macro_rules! vec {
);
}

/// Creates a [`Vec`] containing the arguments.
///
/// `try_vec!` allows `Vec`s to be defined with the same syntax as array expressions.
/// There are two forms of this macro:
///
/// - Create a [`Vec`] containing a given list of elements:
///
/// ```
/// #![feature(more_fallible_allocation_methods)]
/// # #[macro_use] extern crate alloc;
/// let v = try_vec![1, 2, 3]?;
/// assert_eq!(v[0], 1);
/// assert_eq!(v[1], 2);
/// assert_eq!(v[2], 3);
/// # Ok::<(), alloc::collections::TryReserveError>(())
/// ```
///
/// - Create a [`Vec`] from a given element and size:
///
/// ```
/// #![feature(more_fallible_allocation_methods)]
/// # #[macro_use] extern crate alloc;
/// let v = try_vec![1; 3]?;
/// assert_eq!(v, [1, 1, 1]);
/// # Ok::<(), alloc::collections::TryReserveError>(())
/// ```
///
/// Note that unlike array expressions this syntax supports all elements
/// which implement [`Clone`] and the number of elements doesn't have to be
/// a constant.
///
/// This will use `clone` to duplicate an expression, so one should be careful
/// using this with types having a nonstandard `Clone` implementation. For
/// example, `try_vec![Rc::new(1); 5]` will create a vector of five references
/// to the same boxed integer value, not five references pointing to independently
/// boxed integers.
///
/// Also, note that `try_vec![expr; 0]` is allowed, and produces an empty vector.
/// This will still evaluate `expr`, however, and immediately drop the resulting value, so
/// be mindful of side effects.
///
/// [`Vec`]: crate::vec::Vec
#[cfg(not(test))]
#[macro_export]
#[allow_internal_unstable(allocator_api, liballoc_internals)]
#[unstable(feature = "more_fallible_allocation_methods", issue = "86942")]
macro_rules! try_vec {
() => (
$crate::__rust_force_expr!(core::result::Result::Ok($crate::vec::Vec::new()))
);
($elem:expr; $n:expr) => (
$crate::__rust_force_expr!($crate::vec::try_from_elem($elem, $n))
);
($($x:expr),+ $(,)?) => (
$crate::__rust_force_expr!({
let values = [$($x),+];
let layout = $crate::alloc::Layout::for_value(&values);
$crate::boxed::Box::try_new(values)
.map(|b| <[_]>::into_vec(b))
.map_err(|_| $crate::collections::TryReserveError::alloc_error(layout))
})
);
}

// HACK(japaric): with cfg(test) the inherent `[T]::into_vec` method, which is
// required for this macro definition, is not available. Instead use the
// `slice::into_vec` function which is only available with cfg(test)
Expand All @@ -73,6 +137,27 @@ macro_rules! vec {
($($x:expr,)*) => (vec![$($x),*])
}

#[cfg(test)]
#[allow(unused_macros)]
macro_rules! try_vec {
() => (
core::result::Result::Ok($crate::vec::Vec::new())
);
($elem:expr; $n:expr) => (
$crate::vec::try_from_elem($elem, $n)
);
($($x:expr),*) => (
{
let values = [$($x),+];
let layout = $crate::alloc::Layout::for_value(&values);
$crate::boxed::Box::try_new(values)
.map(|b| <[_]>::into_vec(b))
.map_err(|_| $crate::collections::TryReserveError::alloc_error(layout))
}
);
($($x:expr,)*) => (try_vec![$($x),*])
}

/// Creates a `String` using interpolation of runtime expressions.
///
/// The first argument `format!` receives is a format string. This must be a string
Expand Down
Loading

0 comments on commit 90cf90b

Please sign in to comment.