Trait Allocator

pub unsafe trait Allocator

An implementation of Allocator can allocate, grow, shrink, and deallocate arbitrary blocks of data described via [Layout][].

Allocator is mostly designed to be implemented on ZSTs, references, or smart pointers, but can also be implemented directly on the underlying memory-owning type so long as it upholds the necessary guarantees. In general, an allocator of the type MyAlloc([u8; N]) cannot be soundly created without being pinned or otherwise immovable in order to be correct.

In contrast to [GlobalAlloc][], Allocator allows zero-sized allocations. If an underlying allocator does not support this (like jemalloc) or responds by returning a null pointer (such as libc::malloc), this must be caught by the implementation.

In order to be usable in a flexible manner while still being sound, implementors of the trait must uphold very detailed semantics as explained below; the following terms are thus provided as vocabulary for allocator safety and implementation requirements:

Equivalent allocators

Multiple allocator values can sometimes be interchangeable with each other. When this is the case, we refer to those allocators as being equivalent to each other.

Users of allocators may assume the following are true of equivalent allocators, and implementors must ensure these rules are upheld:

Additionally, implementors of Allocator may specify additional equivalences between allocators. It is the responsibility of such implementors to make sure that equivalent allocators have "compatible" Allocator implementations. In particular, the standard library specifies the following equivalences:

Currently allocated memory

Some of the methods require that a memory block is currently allocated by some specific allocator. This means that:

Invalidating memory blocks

A memory block that is currently allocated becomes invalidated when one of the following happens:

Note that these conditions imply that a collection may ensure that any specific currently allocated memory block won't be invalidated by:

Also note that safe public API of an allocator with & access is not allowed to invalidate its memory blocks. Furthermore, unsafe public API of an allocator with & access must document that they invalidate memory blocks (e.g., by calling deallocate) if they do. Therefore, a collection may safely expose & access to its allocator.

Also note that, even in cases where there are other "alive" allocators known to be equivalent to a given collection's allocator, most collections still should not publicly expose &mut access to their allocators. The fact that there are other "alive" allocators would prevent this &mut access from invalidating the collection's memory block, but public &mut access is still likely to be unsound, since a user could replace the collection's allocator with a non-equivalent allocator, causing the collection to deallocate its memory with the wrong allocator.

Memory fitting

Some of the methods require that a layout fits a memory block or vice versa. This means that the following conditions must hold:

Safety

Implementors of Allocator must ensure that a memory block that is currently allocated by the allocator points to valid memory until that memory block is invalidated. The implementor must also not violate this invariant of Allocator via allocator equivalences that are in the implementor's control.

Additionally, any memory block returned by the allocator must satisfy the allocation invariants described in core::ptr. In particular, if a block has base address p and size n, then p as usize + n <= usize::MAX must hold. These blocks must also be wholly disjoint.

This ensures that pointer arithmetic within the allocation (for example, ptr.add(len)) cannot overflow the address space, and that it is possible to perform nonoverlapping copies between allocations.

None of the allocating or deallocating methods may unwind. This restriction may be lifted in the future by ensuring unwinding out of an allocating function always aborts. If an implementor of Allocator also has drop glue or directly implements Drop, dropping the allocator must not result in an unwind.

It is undefined behavior for the allocator to read, write, or deallocate any memory that is currently allocated. This memory is owned by the user; the allocator must not touch it.

Lastly, the methods on this trait must be correct; in particular, the layout requested must be respected, calls must zero out memory if the documentation so requires, returning an AllocError from a reallocating method must indeed ensure that the old pointer was not invalidated, and de/reallocating calls must accept layouts in the ranges defined by their documentation.

Required Methods

fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>

Attempts to allocate a block of memory.

On success, returns a [NonNull<[u8]>][NonNull] meeting the size and alignment guarantees of layout. The returned block may have a larger size than specified by layout.size(), and may or may not have its contents initialized.

It is recommended that overallocating as per the above is only performed if doing so is cheap; there is no guarantee that the caller is able to take advantage of the returned excess. Implementors are free to e.g. provide an alternate method to query available excess if doing so is expensive and should be left to the caller.

Note that the returned block of memory is considered currently allocated with this allocator (and equivalent allocators). Therefore, it is the responsibility of implementors of Allocator to make sure that this block of memory remains valid until it is invalidated.

Errors

Returning Err indicates that either memory is exhausted or layout does not meet allocator's size or alignment constraints.

Implementations are encouraged to return Err on memory exhaustion rather than aborting, but this is not a strict requirement. (Specifically: it is legal to implement this trait atop an underlying native allocation library that aborts on memory exhaustion.)

Clients wishing to abort computation in response to an allocation error are encouraged to call the handle_alloc_error function, rather than directly invoking panic! or similar.

unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout)

Deallocates the memory referenced by ptr.

Safety

  • ptr must denote a block of memory currently allocated via this allocator, and
  • layout must fit that block of memory.

Note that it is immediate language UB for a deallocation or reallocation to invalidate any outstanding references, smart pointers, etc.; thus, notably, an allocator that has been moved into its own currently allocated memory may not have its backing memory be freed, even if the allocator is never used again afterwards. This is due to the fact that such a deallocation would invalidate the &self reference passed to this method.

Provided Methods

fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError>

Behaves like allocate, but also ensures that the returned memory is zero-initialized.

Errors

Returning Err indicates that either memory is exhausted or layout does not meet allocator's size or alignment constraints.

Implementations are encouraged to return Err on memory exhaustion rather than aborting, but this is not a strict requirement. (Specifically: it is legal to implement this trait atop an underlying native allocation library that aborts on memory exhaustion.)

Clients wishing to abort computation in response to an allocation error are encouraged to call the handle_alloc_error function, rather than directly invoking panic! or similar.

unsafe fn grow(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError>

Attempts to extend the memory block.

Returns a new [NonNull<[u8]>][NonNull] containing a pointer and the actual size of the allocated memory. The pointer is suitable for holding data described by new_layout. To accomplish this, the allocator may extend the allocation referenced by ptr to fit the new layout.

If this returns Ok, then the memory block referenced by ptr has been invalidated. The old ptr must not be used to access the memory, even if the allocation was grown in-place. The newly returned pointer is the only valid pointer for accessing this memory now. All bytes past old_layout.size() should be assumed to be uninitialised.

If this method returns Err, then the memory block has not been invalidated, and the contents of the memory block are unaltered.

Safety

  • ptr must denote a block of memory currently allocated via this allocator.
  • old_layout must fit that block of memory (The new_layout argument need not fit it.).
  • new_layout.size() must be greater than or equal to old_layout.size().

Note that new_layout.align() need not be the same as old_layout.align().

Errors

Returns Err if the new layout does not meet the allocator's size and alignment constraints of the allocator, or if growing otherwise fails.

Implementations are encouraged to return Err on memory exhaustion rather than aborting, but this is not a strict requirement. (Specifically: it is legal to implement this trait atop an underlying native allocation library that aborts on memory exhaustion.)

Clients wishing to abort computation in response to an allocation error are encouraged to call the handle_alloc_error function, rather than directly invoking panic! or similar.

unsafe fn grow_zeroed(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError>

Behaves like grow, but also ensures that the new contents are set to zero before being returned.

The memory block will contain the following contents after a successful call to grow_zeroed:

  • Bytes 0..old_layout.size() are preserved from the original allocation.
  • Bytes old_layout.size()..new_size are zeroed. new_size refers to the size of the memory block returned by the grow_zeroed call, which may be larger than new_layout.size().

Safety

  • ptr must denote a block of memory currently allocated via this allocator.
  • old_layout must fit that block of memory (The new_layout argument need not fit it.).
  • new_layout.size() must be greater than or equal to old_layout.size().

Note that new_layout.align() need not be the same as old_layout.align().

Errors

Returns Err if the new layout does not meet the allocator's size and alignment constraints of the allocator, or if growing otherwise fails.

Implementations are encouraged to return Err on memory exhaustion rather than aborting, but this is not a strict requirement. (Specifically: it is legal to implement this trait atop an underlying native allocation library that aborts on memory exhaustion.)

Clients wishing to abort computation in response to an allocation error are encouraged to call the handle_alloc_error function, rather than directly invoking panic! or similar.

unsafe fn shrink(&self, ptr: NonNull<u8>, old_layout: Layout, new_layout: Layout) -> Result<NonNull<[u8]>, AllocError>

Attempts to shrink the memory block.

Returns a new [NonNull<[u8]>][NonNull] containing a pointer and the actual size of the allocated memory. The pointer is suitable for holding data described by new_layout. To accomplish this, the allocator may shrink the allocation referenced by ptr to fit the new layout.

If this returns Ok, then the memory block referenced by ptr has been invalidated. The old ptr must not be used to access the memory, even if the allocation was shrunk in-place. The newly returned pointer is the only valid pointer for accessing this memory now. All bytes past new_layout.size() should be assumed to be uninitialised.

If this method returns Err, then the memory block has not been invalidated, and the contents of the memory block are unaltered.

Safety

  • ptr must denote a block of memory currently allocated via this allocator.
  • old_layout must fit that block of memory (The new_layout argument need not fit it.).
  • new_layout.size() must be smaller than or equal to old_layout.size().

Note that new_layout.align() need not be the same as old_layout.align().

Errors

Returns Err if the new layout does not meet the allocator's size and alignment constraints of the allocator, or if shrinking otherwise fails.

Implementations are encouraged to return Err on memory exhaustion rather than aborting, but this is not a strict requirement. (Specifically: it is legal to implement this trait atop an underlying native allocation library that aborts on memory exhaustion.)

Clients wishing to abort computation in response to an allocation error are encouraged to call the handle_alloc_error function, rather than directly invoking panic! or similar.

Implementors