Skip to content

Commit 90cf90b

Browse files
committed
vec: add try_* methods and a try_vec! macro to make Vec usable in without infallible allocation methods
As a part of the work for #86942 the `no_global_oom_handling` cfg was added in #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.
1 parent 0265a3e commit 90cf90b

File tree

12 files changed

+917
-185
lines changed

12 files changed

+917
-185
lines changed

Diff for: library/alloc/src/collections/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ impl TryReserveError {
7474
pub fn kind(&self) -> TryReserveErrorKind {
7575
self.kind.clone()
7676
}
77+
78+
/// Create a new [`TryReserveError`] indicating that there was an allocation failure.
79+
#[must_use]
80+
#[unstable(feature = "more_fallible_allocation_methods", issue = "86942")]
81+
pub fn alloc_error(layout: Layout) -> Self {
82+
Self { kind: TryReserveErrorKind::AllocError { layout, non_exhaustive: () } }
83+
}
7784
}
7885

7986
/// Details of the allocation that caused a `TryReserveError`

Diff for: library/alloc/src/macros.rs

+85
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,70 @@ macro_rules! vec {
5454
);
5555
}
5656

57+
/// Creates a [`Vec`] containing the arguments.
58+
///
59+
/// `try_vec!` allows `Vec`s to be defined with the same syntax as array expressions.
60+
/// There are two forms of this macro:
61+
///
62+
/// - Create a [`Vec`] containing a given list of elements:
63+
///
64+
/// ```
65+
/// #![feature(more_fallible_allocation_methods)]
66+
/// # #[macro_use] extern crate alloc;
67+
/// let v = try_vec![1, 2, 3]?;
68+
/// assert_eq!(v[0], 1);
69+
/// assert_eq!(v[1], 2);
70+
/// assert_eq!(v[2], 3);
71+
/// # Ok::<(), alloc::collections::TryReserveError>(())
72+
/// ```
73+
///
74+
/// - Create a [`Vec`] from a given element and size:
75+
///
76+
/// ```
77+
/// #![feature(more_fallible_allocation_methods)]
78+
/// # #[macro_use] extern crate alloc;
79+
/// let v = try_vec![1; 3]?;
80+
/// assert_eq!(v, [1, 1, 1]);
81+
/// # Ok::<(), alloc::collections::TryReserveError>(())
82+
/// ```
83+
///
84+
/// Note that unlike array expressions this syntax supports all elements
85+
/// which implement [`Clone`] and the number of elements doesn't have to be
86+
/// a constant.
87+
///
88+
/// This will use `clone` to duplicate an expression, so one should be careful
89+
/// using this with types having a nonstandard `Clone` implementation. For
90+
/// example, `try_vec![Rc::new(1); 5]` will create a vector of five references
91+
/// to the same boxed integer value, not five references pointing to independently
92+
/// boxed integers.
93+
///
94+
/// Also, note that `try_vec![expr; 0]` is allowed, and produces an empty vector.
95+
/// This will still evaluate `expr`, however, and immediately drop the resulting value, so
96+
/// be mindful of side effects.
97+
///
98+
/// [`Vec`]: crate::vec::Vec
99+
#[cfg(not(test))]
100+
#[macro_export]
101+
#[allow_internal_unstable(allocator_api, liballoc_internals)]
102+
#[unstable(feature = "more_fallible_allocation_methods", issue = "86942")]
103+
macro_rules! try_vec {
104+
() => (
105+
$crate::__rust_force_expr!(core::result::Result::Ok($crate::vec::Vec::new()))
106+
);
107+
($elem:expr; $n:expr) => (
108+
$crate::__rust_force_expr!($crate::vec::try_from_elem($elem, $n))
109+
);
110+
($($x:expr),+ $(,)?) => (
111+
$crate::__rust_force_expr!({
112+
let values = [$($x),+];
113+
let layout = $crate::alloc::Layout::for_value(&values);
114+
$crate::boxed::Box::try_new(values)
115+
.map(|b| <[_]>::into_vec(b))
116+
.map_err(|_| $crate::collections::TryReserveError::alloc_error(layout))
117+
})
118+
);
119+
}
120+
57121
// HACK(japaric): with cfg(test) the inherent `[T]::into_vec` method, which is
58122
// required for this macro definition, is not available. Instead use the
59123
// `slice::into_vec` function which is only available with cfg(test)
@@ -73,6 +137,27 @@ macro_rules! vec {
73137
($($x:expr,)*) => (vec![$($x),*])
74138
}
75139

140+
#[cfg(test)]
141+
#[allow(unused_macros)]
142+
macro_rules! try_vec {
143+
() => (
144+
core::result::Result::Ok($crate::vec::Vec::new())
145+
);
146+
($elem:expr; $n:expr) => (
147+
$crate::vec::try_from_elem($elem, $n)
148+
);
149+
($($x:expr),*) => (
150+
{
151+
let values = [$($x),+];
152+
let layout = $crate::alloc::Layout::for_value(&values);
153+
$crate::boxed::Box::try_new(values)
154+
.map(|b| <[_]>::into_vec(b))
155+
.map_err(|_| $crate::collections::TryReserveError::alloc_error(layout))
156+
}
157+
);
158+
($($x:expr,)*) => (try_vec![$($x),*])
159+
}
160+
76161
/// Creates a `String` using interpolation of runtime expressions.
77162
///
78163
/// The first argument `format!` receives is a format string. This must be a string

0 commit comments

Comments
 (0)