rust-lang / libs-team

The home of the library team
Apache License 2.0
116 stars 18 forks source link

ACP: replace use of `Pointee` trait with a `ptr::Metadata` type #246

Open CAD97 opened 1 year ago

CAD97 commented 1 year ago

Proposal

Problem statement

ptr::metadata(*const T) currently returns <T as Pointee>::Metadata. Builtin-blanket-implemented traits are "spooky" and not super well supported, especially with associated types. mem::discriminant encapsulates the builtin trait type projection behind the mem::Discriminant type; doing so for ptr::metadata as well seems prudent.

API sketch

(reordered for improved reading order)

Updating the signature of the existing unstable items:

// mod core::ptr

pub fn metadata<T: ?Sized>(ptr: *const T) -> Metadata<T>;
pub fn from_raw_parts<T: ?Sized>(data: *const (), meta: Metadata<T>) -> *const T;
pub fn from_raw_parts_mut<T: ?Sized>(data: *const (), meta: Metadata<T>) -> *const T;

pub struct Metadata<T: ?Sized>(<T as Pointee>::Metadata);

// impl for Metadata<T>: Copy, Eq, Ord, Hash

impl<T: ?Sized> *const T {
    pub fn to_raw_parts(self) -> (*const (), Metadata<T>);
}

impl<T: ?Sized> *mut T {
    pub fn to_raw_parts(self) -> (*mut (), Metadata<T>);
}

impl<T: ?Sized> NonNull<T> {
    pub fn from_raw_parts(data: NonNull<()>, meta: Metadata<T>) -> NonNull<T>;
    pub fn fn to_raw_parts(self) -> (NonNull<()>, Metadata<T>);
}

Motivating examples or use cases

The primary motivation is that working with Metadata<T> is much more straightforward than with <T as Pointee>::Metadata. When working with generic decomposed pointer of unknown pointee kind, all you want is "the pointer metadata for T" opaquely, and access to knowing the specific metadata type for specific input types isn't particularly useful.

A secondary motivation is that by encapsulating more, it becomes easier to stabilize. Being a built-in, auto-implemented trait, Pointee is fundamentally more involved than a relatively simple Metadata struct. Relatedly, the Pointee trait has potential implications on custom DSTs / pointee kinds, if we want to support those, which can be deferred for longer by using an opaque Metadata struct.

Another potential benefit (and bias of the author) is the ability to extend pointer unsizing coercions to user defined pointerish types. It's not practical to provide unsizing support directly for <T as Pointee>::Metadata, but it can be added relatively straightforwardly for a proper Metadata struct, since it ties the pointer metadata strongly back to the pointee type. Providing unsizing in this way enables user defined pointerish types to get unsizing behavior without needing to carry an actual (potentially dummy) pointer, enabling size optimization for creative custom containers.

(As an example, coercing generational arena handles from pointing to distinct concrete types to some unifiable dyn type.)

It's the ACP author's opinion that having a typed Metadata struct provides a useful middle ground both a more strongly typed pointer metadata API and more loosely/openly typed solution. Metadata can offer conversions to/from specific pointer metadata kinds as we commit to them specifically being used. Additionally, the author believes there is meaningful semantic difference between "slice pointer metadata (length)" and usize and what operations should be immediately/easily accessible to either.

Even without potential lang benefits (i.e. unsizing), the API is in the ACP author's opinion improved by using this "facade" type. (Again, making a comparison to the same done for mem::discriminant.)

Potential answers to unresolved tracking issue questions

(Presented for a holistic picture, not necessarily as requisite answers for this ACP)

The author still wishes that slice-tailed pointer construction describing a too-large pointee could have been made (impossible, unsafe, or to) panic, but admits that this is a) long decided by ptr::slice_from_raw_parts and b) highly impractical in the face of as casts between slice-tail-having types.

Alternatives

Keep the existing unstable functions with the current signatures, but introduce/use a type alias Metadata<T> = <T as Pointee>::Metadata. This provides the readability benefits but leads to the API being a false friend to mem::Discriminant, which does use an opaque type to encapsulate DiscriminantKind.

Keep existing signatures, but introduce a separate typed Metadata struct. The struct is a useful container for manipulating split pointers, but relegating it to an optional addon API limits its utility, even if it would still provide the other benefits.

Don't change anything. The unstable status quo is functional, but doesn't seem like it's going to make progress towards stabilization soon. And a lot of interesting ecosystem potential is gated behind the ability to tear apart pointers, or at least the ability to combine one pointer's address+provenance with another's metadata.

Links and related work

The base temperature as I measure it is that either the API or the lang parts don't quite justify themselves independently, but I believe they definitely do together. But I do still personally think the API part does stand on its own independently as well, with the lang part just as an additional side benefit.

A "no, we don't think so" would also be graciously accepted as a closure of this ACP, and permit me to stop thinking about this extension. 🙂

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 as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

Second, if there's a concrete solution: