Skip to content

allocator: refactor for stabilisation#157428

Open
nia-e wants to merge 17 commits into
rust-lang:mainfrom
nia-e:allocator-refactor
Open

allocator: refactor for stabilisation#157428
nia-e wants to merge 17 commits into
rust-lang:mainfrom
nia-e:allocator-refactor

Conversation

@nia-e

@nia-e nia-e commented Jun 4, 2026

Copy link
Copy Markdown
Member

View all comments

Adds my current proposal per the doc in #156882 and follow-up Zulip conversations (notably for dyn-compat) unstably.

r? libs

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jun 4, 2026
@nia-e nia-e added the A-allocators Area: Custom and system allocators label Jun 4, 2026
Comment thread library/core/src/alloc/mod.rs Outdated
@rust-log-analyzer

This comment has been minimized.

qaijuang

This comment was marked as resolved.

@rust-log-analyzer

This comment has been minimized.

@nia-e

nia-e commented Jun 4, 2026

Copy link
Copy Markdown
Member Author

Note that the no-panic bounds introduced close #156490 and #155746. However, if we want to relax them in the future, we may need to adjust our collection types to be more resilient.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

Comment thread library/alloc/src/str.rs Outdated
Comment thread library/core/src/alloc/mod.rs Outdated
Comment on lines +97 to +98
/// - the allocator is mutated through public API taking `&mut` access (notably,
/// running the allocator's destructor is such a mutation), or

@clarfonthey clarfonthey Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This guarantee seems fine on the surface, but I'm trying to wrap my head around what's actually being guaranteed here. Like, clearly, it'd be wildly unsafe to offer an invalidate_everything method on an allocator that just deletes the backing memory without requiring any of the things that are using it to be dropped, but this feels like it's opening the door for that kind of method "as long as you're careful" which, doesn't make a lot of sense.

Like, I'm trying to gauge what value is being gained by this guarantee and it mostly just feels like it's making things more confusing without actually helping.

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

we're saying that such an invalidate_everything method is allowed to exist, and you can't rely on the allocator having not yet been dropped for soundness. in other words, so long as you hold a &alloc (thus preventing a &mut alloc from being created), you can trust the memory you have is fine; but if you lose the &alloc and get a new one back, your memory might have been scribbled over and you must act as such

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Okay, but wouldn't that be the same thing as the lifetime expiring as before? Technically, even though both of them are written as &A, you've gotten a new &A lifetime in that case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

i think the extra guarantee here is that if you do hold a &mut alloc, you can call methods that take alloc by-shared-ref without worry but you can't pass the actual &mut to an untrusted function and expect your allocator to be okay at the end. but i agree that's not obviously guaranteed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Isn't that just totally breaking the aliasing guarantees, though? Since that &mut reference wouldn't be unique.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

...you know, you make a good point. i'll revisit the reasoning for this, i recall adding this in response to something being brought up

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

nvm, i'm being stupid. the following is the reason:

let mut alloc = SomeAllocator::new();
let ptr = alloc.allocate(...);
alloc.something_by_shared_ref();
// ptr is still guaranteed to be valid
alloc.trusted_method_by_unique_ref();
// ptr is still valid because we know for sure the method is trusted not to mess w/ allocator state
alloc.untrusted_method_by_unique_ref();
// ptr must be assumed to be maybe-invalid even if the lifetime of alloc is not expired and ptr hasn't yet been deallocated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess that I was technically thinking of Box whose lifecycle is intrinsically tied to the lifetime of the allocator parameter, whereas in this case if you just call alloc and dealloc manually the "lifetime" is not really tracked at all. So, yes, mutable borrows can happen on the allocator and it's fine, and you guarantee this doesn't happen by taking a non-mutable borrow.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Seeing this thread from @RalfJung: #157428 (comment)

I think we probably also need to be careful about how we define the relationship between these rules and StaticAllocator, since "lifetime expiration" in those cases refers to the allocator value and not references in that case.

Comment thread library/core/src/alloc/mod.rs Outdated
Comment thread library/core/src/alloc/mod.rs
Comment thread library/core/src/alloc/mod.rs Outdated
Comment thread library/core/src/alloc/mod.rs Outdated
Comment thread library/core/src/alloc/mod.rs Outdated
Comment thread library/core/src/alloc/mod.rs Outdated
Comment thread library/core/src/alloc/mod.rs Outdated
Comment thread library/core/src/alloc/mod.rs Outdated
Comment thread library/core/src/alloc/mod.rs Outdated
@clarfonthey

Copy link
Copy Markdown
Contributor

@rustbot author

(mostly so you can more clearly signal when you think things are ready; I've commented here already so I'll see any additional changes for review as they're made)

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 4, 2026
///
/// [`Pin`]: ../../core/pin/struct.Pin.html
#[unstable(feature = "allocator_api", issue = "32838")]
pub unsafe trait StaticAllocator: Allocator {}

@nia-e nia-e Jun 4, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I dropped the 'static bound here (and thus implicitly in Box::pin_in, etc.); since this trait is about being able to be lifetime-subtyped safely, it would mean that you need to be able to coerce to a StaticAllocator + 'a so the whole guarantee about "this is Actually Static I Promise" has weight. cc @rust-lang/opsem in case i did a bad here

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That's more of a @rust-lang/types question

@rust-log-analyzer

This comment has been minimized.

@maxdexh

maxdexh commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Luckily btreemap_alloc is already its own feature gate :)

@maxdexh

maxdexh commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

The StaticAllocator approach for Pin is unsound, see the issue I just linked from.

@nia-e

nia-e commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

thanks, i hate it :D

the plan for initial stabilisation is to not stabilise the 2nd param on Box, so thankfully this isn't blocking. per conversation on the issue & zulip we probably need some special handling around the fundamentalness of Box, but that'll be its own thing. sigh.

@nia-e

nia-e commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

upd: fixed i hope

@rust-log-analyzer

This comment has been minimized.

@nia-e
nia-e force-pushed the allocator-refactor branch from b66ef35 to 5af9c06 Compare July 17, 2026 17:24
Comment thread library/core/src/alloc/mod.rs Outdated
Comment on lines +596 to +597
/// Implementors must ensure that memory cannot be freed except via a call to
/// `Allocator::deallocate`, and that subtype coercion preserves this invariant.

@theemathas theemathas Jul 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// Implementors must ensure that memory cannot be freed except via a call to
/// `Allocator::deallocate`, and that subtype coercion preserves this invariant.
/// Implementors must ensure that memory cannot be freed except via a call to
/// `Allocator::deallocate`, `Allocator::grow`, `Allocator::grow_zeroed`, or `Allocator::shrink`,
/// and that subtype coercion preserves this invariant.

View changes since the review

Comment on lines +602 to +603
/// `Pin::clone`. Namely, an impl of `StaticAllocator for MyAllocator + 'long` guarantees that an
/// impl of `StaticAllocator for MyAllocator + 'short` would be sound to write.

@theemathas theemathas Jul 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
/// `Pin::clone`. Namely, an impl of `StaticAllocator for MyAllocator + 'long` guarantees that an
/// impl of `StaticAllocator for MyAllocator + 'short` would be sound to write.
/// `Pin::clone`. Namely, an impl of `StaticAllocator for MyAllocator<'long>` guarantees that an
/// impl of `StaticAllocator for MyAllocator<'short>` would be sound to write (for covariant allocators).

View changes since the review

@rust-log-analyzer

This comment has been minimized.

Comment thread library/alloc/src/boxed.rs Outdated
Comment on lines +2535 to +2540
unsafe impl<T, A> AllocatorClone for Box<T, A>
where
T: AllocatorClone,
// Otherwise, the clone impl *here* may unwind.
A: AllocatorClone,
Box<T, A>: Clone,

@theemathas theemathas Jul 18, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
unsafe impl<T, A> AllocatorClone for Box<T, A>
where
T: AllocatorClone,
// Otherwise, the clone impl *here* may unwind.
A: AllocatorClone,
Box<T, A>: Clone,
unsafe impl<T, A> AllocatorClone for Box<T, A>
where
T: AllocatorClone,
// Otherwise, the clone impl *here* may unwind.
A: AllocatorClone,

The Box: Clone bound here is redundant

View changes since the review

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

oop yeah ur right

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

i think we could relax this a little more to A: Allocator + Clone iff we do some abort-on-unwind guard in the clone impl of box, but i'm unsure it's worth it. i guess worth flagging as a thing to consider /shrug AllocatorClone & company could/should be made marker traits as well before they're stabilised so overlapping impls here aren't an issue

Co-authored-by: Tim (Theemathas) Chirananthavat <theemathas@gmail.com>
@rust-log-analyzer

This comment has been minimized.

@maxdexh

maxdexh commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

I think the safety requirements of StaticAllocator are too weak. I believe &Bump satisfies the requirements as they are written. Note that &Bump is its own standalone allocator type regardless of the Bump it is based on. &Bump has no APIs that could clear anything and no Drop. However, after its lifetime expires, the Bump it refers to could become available for mutable access again and allow the memory to be reclaimed through its mutable API, unsoundly.

More generally, in my eyes the source of the confusion is that we allow blocks to be invalidated when all equivalent allocators have their destructor run, are mutably accessed, or have a lifetime expire. Here, the lifetime of &Bump expires, so the other equivalent allocator (the original Bump) may invalidate the blocks on mutable access/drop.

Therefore, I think the requirements should specify that all equivalent allocators must ensure that currently allocated blocks cannot be invalidated, except via a call to Allocator::deallocate. This actually includes the supertyping requirement, since we already specify in Allocator that "Moving, subtyping, unsize-coercing, or trait-upcasting an allocator does not change what the allocator is equivalent to", though we probably want to reemphasize that.

@nia-e

nia-e commented Jul 18, 2026

Copy link
Copy Markdown
Member Author

i agree and intended the wording to reflect that, i think i just forgot about it. the intent of the trait is an assertion that nothing on god's green earth could possibly delete an allocation except a call to deallocate somewhere

@nia-e

nia-e commented Jul 18, 2026

Copy link
Copy Markdown
Member Author

i think this doc change fixes it?

@maxdexh

maxdexh commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

I think this could be written more succinctly but yeah that should be correct

@maxdexh

maxdexh commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

I think struct Invariant<A>(ManuallyDrop<A>, PhantomData<fn(A)>) that only exposes &A can soundly implement StaticAllocator for all A: Allocator + 'static (as an equivalent allocator to its A field), since:

  • None of its borrow-checker lifetimes will ever expire ('static types cannot contain unbounded lifetimes)
  • Nothing gets invalidated on drop
  • No mutable api exists

Therefore, it is never possible for all equivalent versions of the A to have one of the three happen to them.

If this is not sound, the requirements need to be adjusted.

@maxdexh

maxdexh commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

In general, using mem::forget on a 'static allocator must imply that after that, all the equivalent allocators are effectively StaticAllocator. Otherwise Arc::pin is unsound.

(Since we can mem::forget a Pin<Arc>, which causes the drop to never run, so it must never be reclaimed)

@maxdexh

maxdexh commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

But I think the requirements are strict enough to imply that, barring any weird typing quirks we missed.

@rust-log-analyzer

Copy link
Copy Markdown
Collaborator

The job x86_64-gnu-gcc failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
---- [run-make] tests/run-make/alloc-no-oom-handling stdout ----

error: rmake recipe failed to complete
status: exit status: 1
command: cd "/checkout/obj/build/x86_64-unknown-linux-gnu/test/run-make/alloc-no-oom-handling/rmake_out" && env -u RUSTFLAGS -u __RUSTC_DEBUG_ASSERTIONS_ENABLED -u __STD_DEBUG_ASSERTIONS_ENABLED -u __STD_REMAP_DEBUGINFO_ENABLED AR="ar" BUILD_ROOT="/checkout/obj/build/x86_64-unknown-linux-gnu" CC="cc" CC_DEFAULT_FLAGS="-ffunction-sections -fdata-sections -fPIC -m64" CXX="c++" CXX_DEFAULT_FLAGS="-ffunction-sections -fdata-sections -fPIC -m64" HOST_RUSTC_DYLIB_PATH="/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib" LD_LIBRARY_PATH="/checkout/obj/build/x86_64-unknown-linux-gnu/bootstrap-tools/x86_64-unknown-linux-gnu/release/build/run_make_support/a300432cf10d6d08/out:/checkout/obj/build/x86_64-unknown-linux-gnu/stage0/lib/rustlib/x86_64-unknown-linux-gnu/lib" LD_LIB_PATH_ENVVAR="LD_LIBRARY_PATH" LLVM_BIN_DIR="/checkout/obj/build/x86_64-unknown-linux-gnu/ci-llvm/bin" LLVM_COMPONENTS="aarch64 aarch64asmparser aarch64codegen aarch64desc aarch64disassembler aarch64info aarch64utils abi aggressiveinstcombine all all-targets amdgpu amdgpuasmparser amdgpucodegen amdgpudesc amdgpudisassembler amdgpuinfo amdgputargetmca amdgpuutils analysis arm armasmparser armcodegen armdesc armdisassembler arminfo armutils asmparser asmprinter avr avrasmparser avrcodegen avrdesc avrdisassembler avrinfo binaryformat bitreader bitstreamreader bitwriter bpf bpfasmparser bpfcodegen bpfdesc bpfdisassembler bpfinfo cas cfguard cgdata codegen codegentypes core coroutines coverage csky cskyasmparser cskycodegen cskydesc cskydisassembler cskyinfo debuginfobtf debuginfocodeview debuginfodwarf debuginfodwarflowlevel debuginfogsym debuginfologicalview debuginfomsf debuginfopdb demangle dlltooldriver dtlto dwarfcfichecker dwarflinker dwarflinkerclassic dwarflinkerparallel dwp engine executionengine extensions filecheck frontendatomic frontenddirective frontenddriver frontendhlsl frontendoffloading frontendopenacc frontendopenmp fuzzercli fuzzmutate globalisel hexagon hexagonasmparser hexagoncodegen hexagondesc hexagondisassembler hexagoninfo hipstdpar instcombine instrumentation interfacestub interpreter ipo irprinter irreader jitlink libdriver lineeditor linker loongarch loongarchasmparser loongarchcodegen loongarchdesc loongarchdisassembler loongarchinfo lto m68k m68kasmparser m68kcodegen m68kdesc m68kdisassembler m68kinfo mc mca mcdisassembler mcjit mcparser mips mipsasmparser mipscodegen mipsdesc mipsdisassembler mipsinfo mirparser msp430 msp430asmparser msp430codegen msp430desc msp430disassembler msp430info native nativecodegen nvptx nvptxcodegen nvptxdesc nvptxinfo objcarcopts objcopy object objectyaml option orcdebugging orcjit orcshared orctargetprocess passes plugins powerpc powerpcasmparser powerpccodegen powerpcdesc powerpcdisassembler powerpcinfo profiledata remarks riscv riscvasmparser riscvcodegen riscvdesc riscvdisassembler riscvinfo riscvtargetmca runtimedyld sandboxir scalaropts selectiondag sparc sparcasmparser sparccodegen sparcdesc sparcdisassembler sparcinfo support supportlsp symbolize systemz systemzasmparser systemzcodegen systemzdesc systemzdisassembler systemzinfo tablegen target targetparser telemetry textapi textapibinaryreader transformutils vectorize webassembly webassemblyasmparser webassemblycodegen webassemblydesc webassemblydisassembler webassemblyinfo webassemblyutils windowsdriver windowsmanifest x86 x86asmparser x86codegen x86desc x86disassembler x86info x86targetmca xray xtensa xtensaasmparser xtensacodegen xtensadesc xtensadisassembler xtensainfo" LLVM_FILECHECK="/checkout/obj/build/x86_64-unknown-linux-gnu/ci-llvm/bin/FileCheck" PYTHON="/usr/bin/python3" RUSTC="/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" RUSTDOC="/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustdoc" SOURCE_ROOT="/checkout" TARGET="x86_64-unknown-linux-gnu" TARGET_EXE_DYLIB_PATH="/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib/rustlib/x86_64-unknown-linux-gnu/lib" __BOOTSTRAP_JOBS="4" __RMAKE_VERBOSE_SUBPROCESS_OUTPUT="1" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/run-make/alloc-no-oom-handling/rmake"
stdout: none
--- stderr -------------------------------
command failed at line 14
LD_LIBRARY_PATH="/checkout/obj/build/x86_64-unknown-linux-gnu/test/run-make/alloc-no-oom-handling/rmake_out:/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/lib:/checkout/obj/build/x86_64-unknown-linux-gnu/bootstrap-tools/x86_64-unknown-linux-gnu/release/build/run_make_support/a300432cf10d6d08/out:/checkout/obj/build/x86_64-unknown-linux-gnu/stage0/lib/rustlib/x86_64-unknown-linux-gnu/lib" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/run-make/alloc-no-oom-handling/rmake_out" "--edition" "2024" "-Dwarnings" "--crate-type" "rlib" "/checkout/library/alloc/src/lib.rs" "--cfg" "no_global_oom_handling" "--target=x86_64-unknown-linux-gnu"
output status: `exit status: 1`
=== STDOUT ===



=== STDERR ===
error[E0277]: the trait bound `Box<T, A>: Clone` is not satisfied

Important

For more information how to resolve CI failures of this job, visit this link.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-allocators Area: Custom and system allocators S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet