google / google/zerocopy

Permit ergonomically aborting on allocation failure

Open
#1,659 0 comments 1 reaction 0 assignees View on GitHub
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.