Skip to content

Conversation

@frank-king
Copy link

@frank-king frank-king commented Dec 22, 2025

This RFC introduces a Thin wrapper which thinifies a fat pointer of a DST by inlining its metadata.

Rendered.

Pre-RFC on Rust Internal Forum.

Important

When responding to RFCs, try to use inline review comments (it is possible to leave an inline review comment for the entire file at the top) instead of direct comments for normal comments and keep normal comments for procedural matters like starting FCPs.

This keeps the discussion more organized.

@jieyouxu jieyouxu added T-lang Relevant to the language team, which will review and decide on the RFC. T-types Relevant to the types team, which will review and decide on the RFC. labels Dec 23, 2025
pub trait MetaSized: ValueSized {}
```

For `dyn Trait + ValueSized` types, the `MetadataSize` entry of the common vtable entries
Copy link

Choose a reason for hiding this comment

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

Why are you adding these types? No existing code will be able to accept them (because they're ValueSized), also they're annoyingly incompatible with dyn Trait because converting from dyn Trait to dyn Trait + ValueSized requires replacing the VTable with a new one.

Copy link
Contributor

@zachs18 zachs18 Dec 31, 2025

Choose a reason for hiding this comment

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

converting from dyn Trait to dyn Trait + ValueSized requires replacing the VTable with a new one.

I think we could make dyn Trait and dyn Trait + ValueSized (and a hypothetical dyn Trait + PointeeSized) be able to share a vtable by encoding whether the concrete type is Sized or not in the alignment field of the metadata, since the alignment must always be a positive power of two for a Sized type.

Specifically:

// expository
struct Vtable {
  drop_in_place: nullable fn ptr,
  align: usize,
  size: union<usize, fn ptr>,
}

(I'm using Thin here as the current unstable meaning in the stdlib of Pointee<Metadata = ()>, not the Thin type proposed in this RFC)

  1. align > 0: the concrete type is Thin + Sized, the align field is the alignment and the size field is the size.
  2. align == 0 and size != 0/null: the concrete type is Thin + !Sized + ValueSized, the size field is a (non-null) function pointer to fn(*comst _) -> Layout (roughly)
  3. align == 0 and size == 0/null: the concrete type is Thin + !ValueSized + PointeeSized: not actually proposed in this RFC, but would allow unsizing extern types to dyn Trait + PointeeSized.

This shouldn't regress performance for normal dyn Trait (i.e. dyn Trait + MetaSized), since the compiler can assume that only the first case needs to be handled.

For dyn Trait + ValueSized, the compiler will check the align field of the vtable to know whether the layout is given inline, or requires calling the function pointer. As a future possibility, there could be a fallible conversion from pointers to dyn Trait + ValueSized to dyn Trait which succeeds if the vtable is in the first class. (Analogous for dyn Trait + PointeeSized).

Copy link
Author

Choose a reason for hiding this comment

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

Or, how about an (niche-optimized) enum? (Which has better readability)

enum LayoutEntry {
    PointeeSized,
    ValueSized(fn (*const ()) -> Layout),
    MetaSized(Layout),
}

Then

struct VtableHeader {
    drop_in_place: unsafe fn(*mut ()),
    layout_entry: LayoutEntry,
}

Copy link
Author

Choose a reason for hiding this comment

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

Added a section of "adjustments to the common vtable entries" to describe it.

- `List<T>` -> `&Thin<[T]>`
- `ThinVec<T>`, technically `Box<(usize, Thin<[MaybeUninit<T>]>)>` (in representation)
- `ThinBox<T>` -> `Box<Thin<T>>`
- `BoxedTrait` -> `Box<Thin<dyn Trait>>`

Choose a reason for hiding this comment

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

I would love the idea of getting a fully functional ThinBox, as the current version is so functionality-starved.

Do I understand correctly that this would also "automatically" give thin versions of Rc and Arc?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do I understand correctly that this would also "automatically" give thin versions of Rc and Arc?

I don't think so, at least not without additional guarantees or restrictions. In order for Weak<T> to work, Arc<T> needs to be able to get the size/align of a T even after it has been dropped (which is possible for T: MetaSized since it only requires the pointer metadata), so we'd have to also require that for ValueSized types, or special-case Thin<_> somehow. Or maybe we could restrict Weak<T> to T: MetaSized, so only Arc<T>/Rc<T> needs to handle T: ValueSized which I think should be feasible (just get the layout before dropping, if we are in a codepath that could deallocate).

Copy link
Author

Choose a reason for hiding this comment

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

I think Weak also works for Thin<T> where T: MetaSized because the drop for the inlined metadata is a no-op and remains unchanged after Thin<T> being dropped. That metadata can still be readable, though it may need extra modification with the opsem, i.e. a value can be partially initialized after deinitialization where some of its fields are trivially destructible.

Now they can be rewritten as:
- `List<T>` -> `&Thin<[T]>`
- `ThinVec<T>`, technically `Box<(usize, Thin<[MaybeUninit<T>]>)>` (in representation)
- `ThinBox<T>` -> `Box<Thin<T>>`

Choose a reason for hiding this comment

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

I perused the code of ThinBox when re-implementing an equivalent, and noted a fairly clever optimization: when the data portion is ZST, the library const-allocates the associated metadata and simply sets the pointer in Box to point to this "static" element.

(see ThinBox::new_unsize_zst)

Do you believe it would be possible to implement the same trick with Box<Thin<T>>?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think doing so would require clawing back one of Box's current guarantees, that (for non-zero-sized pointees) a Box owns an allocation from the global allocator with the correct layout (source). If we make Box::<Thin<[u8]>>::bikeshed_new_unsize_zst([]) point to a "static" allocation, then this guarantee is no longer true (or at least needs to be restricted to T: MetaSized)

Personally, prefer the current guarantee for Box

However, I think something like this could work for Arc, like how impl Default for Arc<[T]> shares a common empty slice ArcInner. Arc::<Thin<_>>::new_unsize_zst could work basically just like how ThinBox::new_unsize_zst works, but the const allocation would be an ArcInner<Thin<_>> that starts with a nonzero refcount, so the code will never attempt to deallocate it (assuming no-one incurs UB by calling Arc::decrement_..._count too many times).

- add motivation for more thin containers/pointers
- add adjustments to the layout entries in vtable
- add the layout assumption question of thin pointers to unresolved questions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-lang Relevant to the language team, which will review and decide on the RFC. T-types Relevant to the types team, which will review and decide on the RFC.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants