Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 104 additions & 34 deletions library/core/src/alloc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,89 @@ impl fmt::Display for AllocError {
/// 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.
///
/// ### 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.
///
/// The following conditions are sufficient conditions for allocators to be equivalent.
/// * An allocator is equivalent to itself. (Equivalence is reflexive.)
/// * If an allocator is equivalent to a second allocator, then
/// the second allocator is also equivalent to the first. (Equivalence is symmetric.)
Comment thread
nia-e marked this conversation as resolved.
/// * If an allocator is equivalent to a second allocator, and
/// the second allocator is equivalent to a third allocator, then
/// the first allocator is also equivalent to the third allocator.
/// (Equivalence is transitive.)
/// * Moving, subtyping, unsize-coercing, or trait-upcasting an allocator does not change
/// what the allocator is equivalent to.
/// * Copying or cloning allocator results in an allocator that's
/// equivalent to the initial allocator.
///
/// 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:
/// * A reference to an allocator (either `&` or `&mut`) is equivalent to
/// the allocator being referenced.
Comment thread
nia-e marked this conversation as resolved.
/// * A `Box`, `Rc`, or `Arc` containing an allocator is equivalent to
/// the allocator inside.
/// * All `Global` allocator instances are equivalent with each other.
/// * All `System` allocator instances are equivalent with each other.
///
/// Note: Currently, the interaction between cloning and unsize-coercing allocators
/// is unsound, and there is ongoing discussion on how to revise the `Allocator` trait
/// to fix this. See [#156920].
Comment thread
clarfonthey marked this conversation as resolved.
///
/// [#156920]: https://github.com/rust-lang/rust/issues/156920
///
/// ### Currently allocated memory
///
/// Some of the methods require that a memory block is *currently allocated* by an allocator.
/// Some of the methods require that a memory block is *currently allocated* by some specific allocator.
/// This means that:
/// * the starting address for that memory block was previously
/// returned by [`allocate`], [`grow`], or [`shrink`], and
/// * the memory block has not subsequently been deallocated.
/// * the starting address for that memory block was previously returned by
/// the [`allocate`], [`allocate_zeroed`], [`grow`], [`grow_zeroed`], or [`shrink`] methods,
/// called on an allocator that's equivalent to this specific allocator; and
/// * the memory block has not subsequently been [*invalidated*].
///
/// [*invalidated*]: #invalidating-memory-blocks
///
/// ### Invalidating memory blocks
///
/// A memory block is deallocated by a call to [`deallocate`],
/// or by a call to [`grow`] or [`shrink`] that returns `Ok`.
/// A call to `grow` or `shrink` that returns `Err`,
/// does not deallocate the memory block passed to it.
/// A memory block that is currently allocated becomes *invalidated* when one
/// of the following happens:
/// * The memory block is deallocated. This occurs when the memory block
/// is passed as an argument to a [`deallocate`] call, or when it is passed
/// as an argument to a [`grow`], [`grow_zeroed`] or [`shrink`] call that returns `Ok`.
/// * All (equivalent) allocators that this memory block is allocated with,
Comment thread
nia-e marked this conversation as resolved.
/// each has one of the following happen to them:
/// * The allocator's destructor runs.
/// * The allocator is mutated through public API taking `&mut` access.
/// * One of the borrow-checker lifetimes in the allocator's type expires.
///
/// Note that these conditions imply that a collection may ensure that
/// any specific currently allocated memory block won't be invalidated, by:
/// * not deallocating that memory block,
/// * owning an allocator that memory block is allocated with, and
/// * not publicly exposing `&mut` access to that allocator.
Comment thread
theemathas marked this conversation as resolved.
///
/// Also note that accessing an allocator with `&` access cannot invalidate
/// its memory blocks. Therefore, collections may safely expose `&` access
/// to its allocator.
///
/// Also note that, even in cases where are other "alive" allocators known to be
/// equivalent to a given collection's allocator, most collections still should
/// not publicly expose `&mut` access to its allocator. 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.
///
/// [`allocate`]: Allocator::allocate
/// [`allocate_zeroed`]: Allocator::allocate_zeroed
/// [`grow`]: Allocator::grow
/// [`grow_zeroed`]: Allocator::grow_zeroed
/// [`shrink`]: Allocator::shrink
/// [`deallocate`]: Allocator::deallocate
///
Expand All @@ -82,23 +150,20 @@ impl fmt::Display for AllocError {
/// * the memory block must be *currently allocated* with alignment of [`layout.align()`], and
/// * [`layout.size()`] must fall in the range `min ..= max`, where:
/// - `min` is the size of the layout used to allocate the block, and
/// - `max` is the actual size returned from [`allocate`], [`grow`], or [`shrink`].
/// - `max` is the actual size returned from [`allocate`], [`allocate_zeroed`],
/// [`grow`], [`grow_zeroed`], or [`shrink`].
///
/// [`layout.align()`]: Layout::align
/// [`layout.size()`]: Layout::size
///
/// # Safety
///
/// Memory blocks that are [*currently allocated*] by an allocator,
/// must point to valid memory, and retain their validity until either:
/// - the memory block is deallocated, or
/// - the allocator is dropped.
///
/// Copying, cloning, or moving the allocator must not invalidate memory blocks returned from it.
/// A copied or cloned allocator must behave like the original allocator.
///
/// A memory block which is [*currently allocated*] may be passed to
/// any method of the allocator that accepts such an argument.
/// 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 (e.g., via a misbehaving
/// `impl Clone for Box<MyAllocator>`).
Comment on lines +163 to +166

@theemathas theemathas Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any ideas on how to better word this?

View changes since the review

///
/// Additionally, any memory block returned by the allocator must
/// satisfy the allocation invariants described in `core::ptr`.
Expand All @@ -107,7 +172,9 @@ impl fmt::Display for AllocError {
///
/// This ensures that pointer arithmetic within the allocation
/// (for example, `ptr.add(len)`) cannot overflow the address space.
///
/// [*currently allocated*]: #currently-allocated-memory
/// [*invalidated*]: #invalidating-memory-blocks
#[unstable(feature = "allocator_api", issue = "32838")]
#[rustc_const_unstable(feature = "const_heap", issue = "79597")]
pub const unsafe trait Allocator {
Expand All @@ -118,11 +185,13 @@ pub const unsafe trait Allocator {
/// The returned block may have a larger size than specified by `layout.size()`, and may or may
/// not have its contents initialized.
///
/// The returned block of memory remains valid as long as it is [*currently allocated*] and the shorter of:
/// - the borrow-checker lifetime of the allocator type itself.
/// - as long as the allocator and all its clones have not been dropped.
/// 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 points to valid memory until the block is [*invalidated*]
///
/// [*currently allocated*]: #currently-allocated-memory
/// [*invalidated*]: #invalidating-memory-blocks
///
/// # Errors
///
Expand Down Expand Up @@ -178,13 +247,12 @@ pub const unsafe trait Allocator {
/// 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 ownership of the memory block referenced by `ptr` has been
/// transferred to this allocator. Any access to the old `ptr` is Undefined Behavior, even if the
/// allocation was grown in-place. The newly returned pointer is the only valid pointer
/// for accessing this memory now.
/// 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.
///
/// If this method returns `Err`, then ownership of the memory block has not been transferred to
/// this allocator, and the contents of the memory block are unaltered.
/// If this method returns `Err`, then the memory block has not been *invalidated*,
/// and the contents of the memory block are unaltered.
///
/// # Safety
///
Expand All @@ -196,6 +264,7 @@ pub const unsafe trait Allocator {
///
/// [*currently allocated*]: #currently-allocated-memory
/// [*fit*]: #memory-fitting
/// [*invalidated*]: #invalidating-memory-blocks
///
/// # Errors
///
Expand Down Expand Up @@ -305,13 +374,13 @@ pub const unsafe trait Allocator {
/// 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 ownership of the memory block referenced by `ptr` has been
/// transferred to this allocator. Any access to the old `ptr` is Undefined Behavior, even if the
/// allocation was shrunk in-place. The newly returned pointer is the only valid pointer
/// for accessing this memory now.
///
/// If this method returns `Err`, then ownership of the memory block has not been transferred to
/// this allocator, and the contents of the memory block are unaltered.
/// 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.
///
/// If this method returns `Err`, then the memory block has not been *invalidated*,
/// and the contents of the memory block are unaltered.
///
/// # Safety
///
Expand All @@ -323,6 +392,7 @@ pub const unsafe trait Allocator {
///
/// [*currently allocated*]: #currently-allocated-memory
/// [*fit*]: #memory-fitting
/// [*invalidated*]: #invalidating-memory-blocks
///
/// # Errors
///
Expand Down
Loading