Skip to content

Permit ergonomically aborting on allocation failure #1659

Open
@joshlf

Description

@joshlf

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions