You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feature(ptr_metadata) is in feature limbo. Being able to split pointers into raw pointer and metadata unlocks a lot of cool abilities in library code, but as currently architected the metadata APIs expose more than is really comfortable to stabilize in the short term. Notably, Pointee is a very special lang-item, being automatically implemented for every type. Plus, it's unfortunately tied in identity to both "DynSized", extern type, and custom pointee metadata.
This API offers a way to expose a minimal feature(min_ptr_metadata) subset of feature(ptr_metadata) that unblocks the simple use cases of separating address from metadata, while remaining forward-compatible with any1 design for exposing more knobs later on.
In short: this splits feature(ptr_metadata) into granular subfeatures such that min_ptr_metadata might be stabilizable this year.
Motivation, use-cases
My main motivating use case is custom reference-like types.
Consider something like a generational arena. When using an arena, instead of using something like rc::Weak<Object>, you use Handle<Object>, where Handle is some index; perhaps { ix: u32, salt: u32 }. This works well when your arena stores a single sized type; you Arena::<Object>::resolve(&self, Handle<Object>) -> &'_ Object and all is good.
This is simple enough to extend to storing arbitrary sized objects in the arena as well, so long as you use some kind of allocation strategy which can handle the mixed-layout allocations rather than a simple array storage. This also means that your index changes from an element index to a byte index, and you need some way to ensure that a handle is only used for the arena that it came from.
But what about unsized types? For example, in an actor-based game engine (as opposed to an ECS-based solution), you might have Handle<PlayerPawn> and Handle<EnemyPawn>, and want to be able to store a handle to either one as Handle<dyn Pawn>. Without feature(ptr_metadata), such a handle is forced to store some ptr::NonNull<dyn Pawn>, where what actually wants to be stored is { ix: u32, salt: u32, meta: ptr::Metadata<dyn Pawn> }.
Being able to separate pointer metadata from the actual pointer is a space-saving mechanism for such designs in cases where the storage is known to be pinned, as resolving a handle could be implemented to validate the index, salt, address, etc. match and then dereference the stored pointer with its metadata. When the storage is not pinned, however, allowing dyn-erased handles is just not possible, as there is no (stable) way (yet) to graft one pointer's metadata onto another's.
Solution sketches
Due to the specifics of this proposal, the proposed API is a slight evolution of the existing API, and heavily split into multiple features. For continuity, all of these smaller features should be considered implied by the ptr_metadata feature.
The other key removal of surface area of ptr_metadata in min_ptr_metadata is the moving of from_ptr_metadata into a separate feature. This means that the choice of using *mut () or *mut u8 for the untyped pointer is deferred to a separate feature. The alternative is with_metadata[_of].
Other notes on API design choices
It is valid to (as) .cast() between pointers which have the same <T as Pointee>::Metadata type.
This, along with the existing (*const T, usize) -> *const [T] functions, is why <[T] as Pointee>::Metadata is usize rather than a SliceMetadata<T> type.
The metadata size function probably wants to wait for the public exposing of a ValidSize type which is bound to 0..=isize::MAX.
The metadata align function might want to wait for the public exposing of a ValidAlign type which is restricted to powers of two.
The metadata size function is fallible because it's possible to safely construct *const [T] describing too-large objects.
The metadata align function is infallible because I don't see any utility in dynamic alignment requirements, but perhaps it should be fallible for uniformity?
The potential ability to add Pointee as a default bound and make extern type be !Pointee I find intriguing. Doing so might be able to unblock further experimentation with extern type, as the current direction seems to be forbidding the use of extern type in generics which do not opt-in to supporting extern types, if opting in is allowed at all. In such a case, perhaps the trait really should evolve to be DynSized.
This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.
Proposal
Problem statement
feature(ptr_metadata)is in feature limbo. Being able to split pointers into raw pointer and metadata unlocks a lot of cool abilities in library code, but as currently architected the metadata APIs expose more than is really comfortable to stabilize in the short term. Notably,Pointeeis a very special lang-item, being automatically implemented for every type. Plus, it's unfortunately tied in identity to both "DynSized",extern type, and custom pointee metadata.This API offers a way to expose a minimal
feature(min_ptr_metadata)subset offeature(ptr_metadata)that unblocks the simple use cases of separating address from metadata, while remaining forward-compatible with any1 design for exposing more knobs later on.In short: this splits
feature(ptr_metadata)into granular subfeatures such thatmin_ptr_metadatamight be stabilizable this year.Motivation, use-cases
My main motivating use case is custom reference-like types.
Consider something like a generational arena. When using an arena, instead of using something like
rc::Weak<Object>, you useHandle<Object>, whereHandleis some index; perhaps{ ix: u32, salt: u32 }. This works well when your arena stores a single sized type; youArena::<Object>::resolve(&self, Handle<Object>) -> &'_ Objectand all is good.This is simple enough to extend to storing arbitrary sized objects in the arena as well, so long as you use some kind of allocation strategy which can handle the mixed-layout allocations rather than a simple array storage. This also means that your index changes from an element index to a byte index, and you need some way to ensure that a handle is only used for the arena that it came from.
But what about unsized types? For example, in an actor-based game engine (as opposed to an ECS-based solution), you might have
Handle<PlayerPawn>andHandle<EnemyPawn>, and want to be able to store a handle to either one asHandle<dyn Pawn>. Withoutfeature(ptr_metadata), such a handle is forced to store someptr::NonNull<dyn Pawn>, where what actually wants to be stored is{ ix: u32, salt: u32, meta: ptr::Metadata<dyn Pawn> }.Being able to separate pointer metadata from the actual pointer is a space-saving mechanism for such designs in cases where the storage is known to be pinned, as resolving a handle could be implemented to validate the index, salt, address, etc. match and then dereference the stored pointer with its metadata. When the storage is not pinned, however, allowing
dyn-erased handles is just not possible, as there is no (stable) way (yet) to graft one pointer's metadata onto another's.Solution sketches
Due to the specifics of this proposal, the proposed API is a slight evolution of the existing API, and heavily split into multiple features. For continuity, all of these smaller features should be considered implied by the
ptr_metadatafeature.The key change is that rather than expose concrete metadata types, we only expose a single uniform opaque
ptr::Metadata<T>type. This can have additional other benefit -- one key one I'm interested in is thatptr::Metadata<T>canCoerceUnsized, allowing pointer-like types wrappingptr::Metadatato get unsizing coercions as if they were holding an actual pointer.The other key removal of surface area of
ptr_metadatainmin_ptr_metadatais the moving offrom_ptr_metadatainto a separate feature. This means that the choice of using*mut ()or*mut u8for the untyped pointer is deferred to a separate feature. The alternative iswith_metadata[_of].Other notes on API design choices
as).cast()between pointers which have the same<T as Pointee>::Metadatatype.(*const T, usize) -> *const [T]functions, is why<[T] as Pointee>::Metadataisusizerather than aSliceMetadata<T>type.core::ptr::WellFormedis a raw pointer that is guaranteed non-null and aligned, per Commit to safety rules for dyn trait upcasting rust#101336sizefunction probably wants to wait for the public exposing of aValidSizetype which is bound to0..=isize::MAX.alignfunction might want to wait for the public exposing of aValidAligntype which is restricted to powers of two.sizefunction is fallible because it's possible to safely construct*const [T]describing too-large objects.alignfunction is infallible because I don't see any utility in dynamic alignment requirements, but perhaps it should be fallible for uniformity?Pointeeas a default bound and makeextern typebe!PointeeI find intriguing. Doing so might be able to unblock further experimentation withextern type, as the current direction seems to be forbidding the use ofextern typein generics which do not opt-in to supporting extern types, if opting in is allowed at all. In such a case, perhaps the trait really should evolve to beDynSized.Links and related work
What happens now?
This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.
Footnotes
Asserted without proof. ↩