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 Results 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., this try_reserve function could fail because the user passed an additional that would cause the Vec's size to overflow isize - this cannot be represented using Layout). As a result, we need from_failed_allocation to accept Layouts that might be basically nonsense placeholders like we pass from try_reserve.
See also: #528
As of #1478 and #1658, allocation failures in our API are returned as
Result::Errrather than being handled by aborting/panicking (e.g. viahandle_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_erroronResult<T, AllocError>Using this extension trait:
...a caller with a
r: Result<T, AllocError>can just callr.unwrap_or_handle_alloc_error().The downside to this approach is that
handle_alloc_errorrequires aLayoutargument. In order to supply this, we'd need to haveAllocErrorcarry aLayout, which would make ourResults larger and add optimizer pressure.Parametrize over the error type
We make allocation errors generic with an
AllocErrortrait which can be constructed from aLayout. We also implement this trait for!- this implementation diverges by callinghandle_alloc_errorwhen constructed.A caller can write
try_reserve::<_, !>(v, additional).into_ok()to recover the infallible behavior ortry_reserve::<_, AllocErr>(v, additional)to keep the fallible behavior.This has a number of downsides:
Layoutfor a failed allocation (e.g., thistry_reservefunction could fail because the user passed anadditionalthat would cause theVec's size to overflowisize- this cannot be represented usingLayout). As a result, we needfrom_failed_allocationto acceptLayouts that might be basically nonsense placeholders like we pass fromtry_reserve.