alloc: stabilise Allocator - #156882
alloc: stabilise Allocator#156882
Allocator#156882Conversation
|
r? @Amanieu |
This comment has been minimized.
This comment has been minimized.
|
I believe the safety requirements are not yet correct. See #156544 |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
APIs can be "renamed" without using an edition, it involves making a new API with the desired name and deprecating the old one, e.g. I'm sympathetic both to the idea that it would feel somewhat silly to deprecate an API shortly after stabilizing it (like with the aforementioned |
|
Also worth noting that if you're using The idea about having space to change this was not really about renaming them (although we could deprecate + add aliases), but rather about the idea of having a more generic API that doesn't require composing method names together. Such a generic API would need its own ACP and would ultimately just be a simpler form of the methods, so, in libs we generally prefer just having individual methods to trying to come up with a trait that covers every use case: people can provide their own traits if they think they're useful, and only the methods are strictly required. (Also, as an aside, I am looking to update our documentation on stabilisations to explain stuff like this a bit better, since we historically haven't been good about maintaining that documentation.) |
|
From the code I've written in std with these functions: moving stuff out of a collection/smart ptr into another one and then using |
|
Also explicitly stating that I trust Nia to mention if anything weird comes up during rebase and you can r=me once FCP passes. |
| pub struct BinaryHeap< | ||
| T, | ||
| #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, | ||
| #[unstable(feature = "allocator_ext", issue = "32838", implied_by = "allocator_api")] A: Allocator = Global, |
There was a problem hiding this comment.
Should this get a fresh tracking issue since that one is pretty old and cluttered?
(rename+alias could optionally merge now separately, to trim this PR to the FCP-relevant bits)
There was a problem hiding this comment.
yeah splitting up the issues should probably be its own PR but I'm happy to do it. i think keeping the stuff in this first stabilisation as allocator_api makes sense (at least symbolically)
|
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. |
|
in the midst of it being so over, i found there was within me an insurmountable "we're so back" |
|
Now that the FCP has ended, I assume the last remaining task is to rebase this PR? I'm not suggesting that there's any cause to rush, though I will note that September 25 is the branch date for Rust 1.100, if there was any desire to have allocators stable for Rust's auspicious triple-digit release. :P |
I know this wasn't fully serious but I'd rather have it at the start of a release cycle so, if needed, we can adjust or add to / subtract from the newly stable API without needing riskier backports. 101 is still a cool number! The release blog post could allocate dalmatians. |
|
I mean, I do recall Nia specifically being rather invested in getting this out in 1.100 but she's technically been on vacation for the past two weeks. We've poured over the semantics of allocators for the past several weeks and it does seem like we would be fine if we merged this right before the beta branch. And besides, if there are regressions, we do have an entire release cycle to backport fixes, or backport the stabilisation if it's really that bad. |
|
i had been knocked out cold by rustconf but i'm back ^^ i'll rebase this asap & separate out the relevant issues so this can be merged |
|
This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
|
rebase is done, we have a new issue for allocator_ext, fcp passed... and now i can sleep. this will conflict with a lot of stuff, but should be fine to rollup if it doesn't touch allocator stuff. @bors r=clarfonthey |
|
📋 This PR cannot be approved because it currently has the following label: |
|
Looks like the main reference ask is for updating fundamental's rules for boxes and noalias only applying to Global. |
|
@bors r+ |
Psst 🙂 |
|
oopers ^^ done, ty! |
View all comments
Allocator stabilisation report
Reference PR:
Box& allocator interactions reference#2364This is the stabilisation report for a subset of the feature
allocator_api, with tracking issue #32838 under the purview of wg-allocators, initially proposeed by RFC #1398. The remainder of the feature will be renamed toallocator_ext.This was a collaborative effort of t-libs, wg-allocators, members of t-types, t-lang, and t-opsem, alongside interested parties in the ecosystem and contributors to the initial attempt at stabilisation on GitHub.
See also the new wg-allocators roadmap on the matter.
Summary
The following is a proposal following several conversations, in-person and online, with libs team members and interested ecosystem participants and represents an attempt at stabilising an MVP for the
Allocatortrait and its implementation safety requirements, alongside minimal functionality to make its use possible in the standard library.While an effort was made to align with the stated positions of the team, the opinions and rationale stated are the author's own, and should not be seen as representative of the libs(-api) team as a whole except insofar as individual members therein choose to endorse the contents of report. Any mention of "we", "us", etc. should be understood to refer to the author alongside those who have explicitly expressed agreement.
API & considerations
The stabilised API surface consists of:
The
by_refmethod onAllocatorwas removed, as it was only a postfix syntax convenience (equivalent to writing(&alloc)).The safety requirements on implementors of
Allocatorwere tightened to the most restrictive sound form we expect to possibly want, in order to enable us to iterate on the design in the future and relax these bounds if it is deemed possible. Notable changes from the form assumed before the stabilisation effort started:Clone for Arc<T, A>implementation relied on but was improperly documented, for implementors not to invalidate allocated memory on drop or mutable access was made explicit;Allocator + Clonemust uphold were moved to their own unstable marker subtrait as the requirement was deemed unjustifiable.Additionally, it was decided that
Allocatorwill bedyn-compatible, as the resulting constraints on the design were deemed acceptable given the significant increased flexibility for users.Soundness developments
The process of attempting stabilisation resulted in several soundness issues arising, especially with regard to the interaction between custom allocators and
Boxes. Thus, some points had to be adjusted:Allocatoris alsoClone, which constituted possible UB if broken. These have been dropped, as unsafe implementors cannot guard against possible unsoundness from incorrect implementations in downstream safe code, but the equivalent functionality may be added backwards-compatibly with an unsafe marker trait or language mechanism;Box::into_pinwill not yet be possible with custom allocators. This is because of a soundness bug relating to an interaction between the possibility of manually implementingClone for Box<T, A>andBoxbeing covariant overA, allowing for a pinned box to be cloned with a non-'staticallocator from one with a correct'staticallocator subtyped to a non-static one. MakingBoxinvariant over the allocator was considered, but was deemed far too limiting and would technically be a breaking change to reverse later. Thus, for now, an unstable and unsafe marker traitStaticAllocatorwill be introduced to mark an allocator as guaranteeing that its allocations live for'static(i.e. will never be lost unless explicitly de/reallocated). This will be implemented for theGlobalandSystemallocators;Pin's preexisting implementation of a safePin::newfor any pointer type where<Ptr as Deref>::Target: Unpin, it will be stably possible to callPin::new()on a box with a custom allocator as changing this would require significant special-casing in trait resolution. Experiments in this direction surfaced a soundness bug, addressed by tightening the requirements ofimpl PinSafePointer for Boxto necessitate a pin-safeStaticAllocator;Boxhadnoaliassemantics for its pointer if and only if the allocator isGlobal- alongside a similar hack to make "unleaking" work - is to be moved to an unstable wrapper typeNativeAllocator<A: StaticAllocator>, which enables us to make use of LLVM's new allocator intrinsics.Globalwould then be equivalent toNativeAllocator<A>with the concrete allocator substituted in. Per a conversation with members of opsem, this appeared to be a reasonable way forward;Allocator, clones, or drops.Backwards-compatible changes
Several designs were considered to extend or modify the trait's semantics. We have opted to defer full consideration of many of these for later, as we have determined they can be added backwards-compatibly to the existing API. A list of these is present below, alongside rationale for their postponement.
StoreAPIThis is an alternative, more complex proposal for custom allocators (see the draft RFC). Per a conversation in-person with one of the authors of the
Storeproposal, we have established that it could be added backwards-compatibly (inStoreterminology, the stabilisedAllocatortrait is effectively a storage with pointer handles). The details were thus deferred for potential post-stabilisation changes.Split
DeallocatortraitSupertrait item shadowing alongside a blanket
impl<A: Allocator + ?Sized> Deallocator for Awill allow us to add aDeallocatorsupertrait backwards-compatibly, and to relax the requirements for collection types to insted hold aDeallocator. Conversations with those involved in the above issue suggest it is likely for a PR implementing this to be merged in the near future.fn reallocate()The current design uses dedicated
grow,grow_zeroed, andshrinkmethods instead of a way to reallocate between arbitrary sizes. However, such a function could be added with a defaulted body in the future, forwarding to the extantgrow/shrinkimplementations.Conditional reentrancy in
stdNot all allocators will be reentrant in
std, and thus the standard library may want to be able to conditionally call the global allocator in areas it has otherwise promised not to. Thus, the proposed unstableGlobalAllocator: Allocatormarker trait could be extended with a defaulted associated constantREENTRANT_IN_STD: bool = truewherein implementors could promise that a certain allocator never calls any part ofstd.Possible but less clean additions
Several options appeared to signal compelling usecases, but were sufficiently niche that we did not consider them to be blocking for an MVP stabilisation so long as it was realistically possible to express their semantics.
Associated constants
Several usecases would be facilitated by having certain associated items on the
Allocatortrait; notably,const MIN_ALIGN: usizefor the minimum alignment an allocator is always guaranteed to return. Adding the semantics of these backwards-compatibly would rely on maybe trait bounds being stabilised, which per conversations with the lang & types teams we believe is feasible in the near future. Alternatively, much of the same functionality could be added with defaultedconstmethods, which are also on the stabilisation path.grow_in_place()There is currently no obvious way to signal through the API whether a move of the data is acceptable when reallocating memory. Though messy, a way to express these semantics with the current design does exist, even if non-obvious:
We have decided that this is acceptable, given that it is "only" a point of design and not underlying functionality. A cleaner way to signal such semantics would be of interest for future extensions to the trait.
Notably, in-place growing and/or shrinking without invalidating preexisting pointers (i.e. actually changing the size of the allocation in the abstract machine) needs proper support from LLVM which may not happen in the near future.
Allocation flags
A similar transmute-based mechanism as for the above can be used to reference a local inside of the allocator, though this could be UB-prone. Alternatively, and much more nicely, argument splatting could allow us to backwards-compatibly extend the trait (assuming implementors as well as callers may ignore optional fields). However, this would depend on the details of such a proposal.
The main stakeholder who approached us with concerns on this topic - Rust for Linux - signalled willingness to maintain a downstream extension trait for such functionality for the time being.
Rejected alternative proposals
The following changes were explicitly not made to the API pre-stabilisation, despite it being unlikely that their semantics could be nicely expressed in the (near) future. In all cases, notable arguments existed to make the requested change, but we decided they were not sufficiently compelling. Should a way to express these semantics emerge in the future backwards-compatibly, we would be open to re-reviewing them.
NonZeroLayoutargumentsAn idea had been proposed to change the signature of the allocating/freeing methods to take a
Layoutthat is guaranteed to have a nonzero size.We determined that API cleanliness and potential simplification of library code (once
const Traits are stable, collection types could drop special-case logic when using aconst Allocatorat zero capacity) outweigh the arguments for not allowing zero-sized allocations. As we see it, in the cases where it would genuinely be problematic, this will only move the branch on zero-sized allocation to the other side of the call. At worst, it would put marginally more pressure on a branch predictor.Though the possibility of zero-size allocations being probematic is often mentioned, we have not seen sufficiently convincing concrete cases where this is the case. One pointed-to example was that of highly performance-sensitive allocators (e.g. bump allocators); however, it appears most of these cases can trivially support zero-size allocations (e.g. bumping by zero). Consequently, we have decided to keep the nicer logic for downstream users of the trait. An argument had also been made around
jemallocbeing unable to correctly handle zero-sized allocations, but this appears to only apply to internal APIs.A similar idea wherein
allocatewas an unsafe method and support for zero-sized allocations was implementation-defined was rejected on similar usability grounds. Several members of the libs team expressed their opinion that the design ofGlobalAlloc(featuring a similar unsafe allocating method wherein the caller must guarantee the size is nonzero) was not desirable in hindsight.NonNull<u8>return typeLacking a better way to signal returned vs. requested capacity, and not wishing to duplicate all of the allocating methods, we have decided that we would prefer to keep the wide-pointer return value and potentially use that logic to determine capacity in returned allocations. This will never be an issue with regard to performance, as no architecture allocates expects fewer than two registers to be clobbered by a function call, and so there is no cost to returning the wide pointer.
Some callers will elect to ignore extra capacity; similarly, some implementors will elect not to offer it. The language around implementation safety ensures that these cases are supported, and recommends that implementors not signal extra capacity if it would be expensive to do so. That is to say, both the caller and implementor must cooperate for the excess to be meaningfully usable; otherwise there is no performance impact in a correct implementation.
Associated types
Having an associated type, especially for the returned error on allocation failure, had been mentioned as a possible addition; however, doing so would add significant complexity to the trait while also making
dyn-compatibility impossible. Few concrete usecases came up where the allocator itself has meaningful error information that would be actionable to callers, and therefore it was elected to keep the current ZSTAllocError.Future work
A large part of the standard library will need review as we determine what the correct way is for various collection and pointer types to work with custom allocators.
Notably, there are multiple outstanding proposals for integrating fallible allocation APIs into the standard library, and a stable mechanism needs to be decided on for exposing the
Allocator+Cloneinteraction.Outlined potential extensions
The following is a possible future outline of what the
Allocatortrait and related might look like under this proposal, assuming both of supertrait item shadowing and defaulted associated items being added:cc @rust-lang/libs @rust-lang/libs-api @rust-lang/opsem
r? libs