Description
See also: #528
As of #1478 and #1658, allocation failures in our API are returned as Result::Err
rather than being handled by aborting/panicking (e.g. via handle_alloc_error
). This is maximally flexible, but introduces an ergonomics burden to users who prefer the old behavior.
Here are two possible designs to make this more ergonomic.
Provide handle_alloc_error
on Result<T, AllocError>
Using this extension trait:
trait ResultAllocErrorExt<T> {
fn unwrap_or_handle_alloc_error(self) -> T;
}
impl<T> ResultAllocErrorExt<T> for Result<T, AllocError> {
fn unwrap_or_handle_alloc_error(self) -> T {
match self {
Ok(t) => t,
Err(AllocError) => handle_alloc_error(todo!()),
}
}
}
...a caller with a r: Result<T, AllocError>
can just call r.unwrap_or_handle_alloc_error()
.
The downside to this approach is that handle_alloc_error
requires a Layout
argument. In order to supply this, we'd need to have AllocError
carry a Layout
, which would make our Result
s larger and add optimizer pressure.
Parametrize over the error type
We make allocation errors generic with an AllocError
trait which can be constructed from a Layout
. We also implement this trait for !
- this implementation diverges by calling handle_alloc_error
when constructed.
trait AllocError {
fn from_failed_allocation(layout: Layout) -> Self;
}
struct AllocErr;
impl AllocError for AllocErr {
fn from_failed_allocation(_layout: Layout) -> Self {
AllocErr
}
}
impl AllocError for ! {
fn from_failed_allocation(layout: Layout) -> Self {
handle_alloc_error(layout);
}
}
fn try_reserve<T, E: AllocError>(v: &mut Vec<T>, additional: usize) -> Result<(), E> {
v.try_reserve(additional).map_err(|_| {
let layout = unsafe { Layout::from_size_align_unchecked(0, 1) };
E::from_failed_allocation(layout)
})
}
A caller can write try_reserve::<_, !>(v, additional).into_ok()
to recover the infallible behavior or try_reserve::<_, AllocErr>(v, additional)
to keep the fallible behavior.
This has a number of downsides:
- Since type defaults are not supported on functions, the caller must always specify the error type at the call site
- We cannot always synthesize a valid
Layout
for a failed allocation (e.g., thistry_reserve
function could fail because the user passed anadditional
that would cause theVec
's size to overflowisize
- this cannot be represented usingLayout
). As a result, we needfrom_failed_allocation
to acceptLayout
s that might be basically nonsense placeholders like we pass fromtry_reserve
.