Implement pointee metadata unsizing via a TypedMetadata container by CAD97 · Pull Request #97052 · rust-lang/rust (original) (raw)

TL;DR:

// std::ptr struct TypedMetadata<T: ?Sized>(pub ::Metadata); impl<T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<TypedMetadata> for TypedMetadata {}

See src/test/ui/coercion/coerce-just-metadata.rs for an example of this working.

The motivating example is being able to spell a type like

struct SillyBox<T: ?Sized> { data: ptr::NonNull<()>, meta: TypedMetadata, }

and implement CoerceUnsized for it. This is shown to work in src/test/ui/coercion/silly-box.rs.

It may be desirable to support coercing DynMetadata<T> in the future (e.g. DynMetadata<dyn Trait + Sync> -> DynMetadata<dyn Trait>). This PR does not add this, but it could be added fairly easily by adding a CoerceUnsized impl for DynMetadata and allowing DynMetadata where TypedMetadata is checked for here.


FAQ

Isn't TypedMetadata<T> just a type alias for <T as Pointee>::Metadata?

Yes and no. The main problem at hand is that <T as Pointee>::Metadata no longer remembers what T is once the projection has been resolved. TypedMetadata<T> then is a "strongly typed alias" which reintroduces T and provides the ability to implement CoerceUnsized, which cannot be directly implemented on a resolved <T as Pointee>::Metadata.

There is a potential alternative -- instead of getting impl CoerceUnsized to work for TypedMetadata<T> as a builtin and then using the existing struct behavior for impl CoerceUnsized, we could teach coercion (and impl CoerceUnsized) to recognize the <T as Pointee>::Metadata projection and directly coerce that (as is currently done in TypedMetadata<T>) rather than delegating to the field's CoerceUnsized implementation. (However, this may be impractical, as type projections tend to get normalized away.)

This would make TypedMetadata a pure library type, but it would still be necessary if wanting to coerce/convert <T as Pointee>::Metadata in a function body or for some concrete T, rather than as a generic struct field.

Why a coercion instead of just a conversion?

Such that SillyBox<T> can be coerced. (See also the next question.)

A conversion is fairly simple to write: just ptr::from_raw_parts a null data pointer and the metadata, then coerce that.

Why not use *mut T instead of (*mut (), <T as Pointee>::Metadata)?

The short version: because you might not always have a pointer.

There are a few reasons you might want to elide storing the actual pointer-to-T, or store something other than a pointer alongside the pointee metadata.

One such case is if you have some allocation at a statically known location; in this case it could make sense to have a StaticRef which just stores the pointee metadata and implements Deref by constructing a normal reference from the raw parts of the static location and the stored metadata. Normally such usage would necessarily also know the full concrete type and thus not need to use non-() metadata, but situations such as using linker functionality to place a slice at a (linker-known) static address could want to have StaticRef<[T]> to avoid re-deriving the linker-provided length multiple times.

Another such case is the use of something like an arena allocator, or a compacting allocator, or any storage allocation which doesn't hand out raw pointers and pin the allocated memory. Here, your ArenaBox<T>/ArenaRef<T> would store the index into the arena (and perhaps additional metadata like a generation) and the pointee metadata, and then you ask the arena to temporarily resolve the ArenaRef<T> into a normal &T.

Yet another is StackBox<T>, which stores a potentially unsized pointee in its own inline memory, allowing for the use of unsized types (such as dyn Trait) without indirection by always reserving a fixed max size/align for them on the stack.

All of these could be serviced by storing the metadata on a dangling pointer; however, this extra stored pointer exists solely to appease the compiler, and not for any actual benefit to the program.