Skip to content

Conversation

@izagawd
Copy link

@izagawd izagawd commented Nov 24, 2025

This RFC proposes #[exhaustive] traits to enable sound cross-trait casting for trait objects.

For any concrete type T, the set of #[exhaustive] traits it implements is finite and deterministic, allowing runtime checks like “if this dyn A also implements dyn B, cast and use it.”

The design adds a per-type exhaustive trait→vtable map and enforces four rules (type-crate ownership of implementation, trait arguments determined by Self, object safe, and 'static only) to keep the mapping coherent under separate compilation.

Use cases include capability-based game entities (e.g., Damageable, Walkable traits) and GUI widgets (e.g., Clickable, Scrollable),
avoiding manual registry/macro approaches such as bevy_reflect.

This enables patterns such as: "if dyn Character is dyn Flyable, then character.fly()"

Rendered

@ehuss ehuss 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 Nov 25, 2025
@burdges
Copy link

burdges commented Nov 26, 2025

We could avoid Rule 1 by building the vtable lookup table externally in ./target, no?

As &'static [(TypeId, TraitVTable)] is a nasty lookup, https://internals.rust-lang.org/t/casting-families-for-fast-dynamic-trait-object-casts-and-multi-trait-objects/12195 proposed defining fresh indices for each "casting family", so that lookup takes place in &'static [TraitVTable]. In that, the families kept the vtable lookup tables small, but this could be avoided perhaps. And it could all be done via procmacros, without changing rustc.

@izagawd
Copy link
Author

izagawd commented Nov 26, 2025

I made another RFC: #3888.

If that lands, rather than having every trait object being forced to store metadata for casting, there could be a

trait Castable: 'static {
    const self EXHAUSTABLE_IMPLEMENTATIONS: &'static [(TypeId, TraitVTable)];
}

which could have a blanket implementation of:

impl<T: 'static> Castable for T {
   const self EXHAUSTABLE_IMPLEMENTATIONS: &'static [(TypeId, TraitVTable)] = std::intrinsics::exhausive_implementations::<T>();
}

Which would make the metadata for casting opt in

Though this will require a good chunk of changes to the exhaustive_traits RFC

It would be ideal to make this Castable trait use final for the EXHAUSTIBLE_IMPLEMENTATIONS const self field. final coming from this RFC: #3678, as making the EXHAUSTIBLE_IMPLEMENTATIONS final would be better than having a blanket implementation. The exhaustible_trait RFC doesnt allow exhaustible traits to have blanket implementations after all, so the final keyword would do wonders. It would be weird that the Castable trait itself was not considered as exhaustible.

Of course we can do compiler magic, and make the Castable trait the only exhaustible_trait that is blanket implemented, but that does not seem ideal to me if we can just do final const self EXHAUSTABLE_IMPLEMENTATIONS: &'static [(TypeId, TraitVTable)] = std::intrinsics::exhausive_implementations::<Self>();

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.

3 participants