Skip to content

Permit ergonomically aborting on allocation failure #1659

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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions