Permit ergonomically aborting on allocation failure
- Dominant language
- Rust
- Stars
- 2.6k
- Forks
- 179
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 29
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`](https://doc.rust-lang.org/std/alloc/fn.handle_alloc_error.html)). 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`
Using this extension trait:
```rust
trait ResultAllocErrorExt {
fn unwrap_or_handle_alloc_error(self) -> T;
}
impl ResultAllocErrorExt for Result {
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` 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.
```rust
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(v: &mut Vec, 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 `Layout`s that might be basically nonsense placeholders like we pass from `try_reserve`.
Contributor guide
Assessment
This issue has not been assessed yet.