From 543b5a766cdea4ac0d664f0e37d505cbae4ec245 Mon Sep 17 00:00:00 2001 From: theiz Date: Mon, 24 Nov 2025 18:42:05 -0400 Subject: [PATCH 01/14] Exhaustive traits --- text/0000-exhaustive-traits.md | 232 +++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 text/0000-exhaustive-traits.md diff --git a/text/0000-exhaustive-traits.md b/text/0000-exhaustive-traits.md new file mode 100644 index 00000000000..d2c5facb559 --- /dev/null +++ b/text/0000-exhaustive-traits.md @@ -0,0 +1,232 @@ +- Feature Name: `exhaustive_traits` +- Start Date: 2025-11-24 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary +For any concrete type T, the set of #[exhaustive] traits it implements is finite and discoverable at runtime, enabling cross-trait casts. + +Given: +```rust +#[exhaustive] +trait Behavior { ... } +``` + + +If I have dyn Trait, I want to be able to attempt: +```rust +let any : &dyn Any = &MyStruct::new(); +let casted: Option<&dyn Behavior> = any.cross_trait_cast_ref::() +``` +# Motivation +[motivation]: #motivation + +It will enable dyn trait pattern matching, which will also enable many other ways of making programs. + +Say you are making a game, and your bullet collides with an entity. If you want to damage it, you would want to check if the object has a `Damageable` trait so you can call its damage method, assuming not everything in the game can be damaged. This method can be seen as another way of composition. A different pattern from having a collection of components (`Vec>`), or the ECS pattern. + +`bevy_reflect`, which is used in the game engine “bevy”, has functionality that enables you to cast between unrelated traits. + +But it involves macros and you have to manually register the trait to enable cross trait casting, and tell the struct “yeah you can be casted to this trait even if the compiler does not know your concrete type” + +GUI/widget capabilities: `Clickable`, `Draggable`, `Focusable`, `Scrollable`, etc. In GUI frameworks, you may want “if this widget supports X, call X” at runtime without a giant enum or manual registration. + +Making casting between unrelated traits a natural part of the language would make this much easier +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + + +If a trait is marked `#[exhaustive]`, then the compiler enforces certain rules that make cross trait casting work soundly. + +### Rule 1:A crate may only implement an exhaustive trait for types it owns. + +Equivalently: + +* `impl SomeTrait for LocalType {}` is allowed. +* `impl SomeTrait for Vec {}` is rejected (type not owned). +* Blanket impls for the trait are also not allowed + +The core problem with cross-trait casting is that two unrelated crates could each see the “same” concrete type but a different set of trait impls, due to downstream impls and blanket impls. With separate compilation, that makes any global “type → trait set” table incoherent unless you delay codegen or centralize it, both of which are hostile to Rust’s model. + +`#[exhaustive]` sidesteps this by making the implementation set for a given type deterministic and crate-local: + +* Every impl of an exhaustive trait for `T` must live in `T`’s defining crate. +* Therefore no other crate can add impls later. +* Therefore all crates see the same exhaustive-trait set for a type. + +This makes a *restricted* form of cross-trait casting feasible. + + +This rule applies **even to the crate that defines the exhaustive trait**. Ownership of the **type** is what matters, not ownership of the trait. + +### Rule 2: An impl is only allowed if the trait’s generic arguments are fully determined by the implementing type. + +Concretely: in +`impl<...> ExhaustiveTrait for SelfTy`, +every generic parameter that appears in `TraitArgs...` must also be a generic parameter of `SelfTy`, or be a concrete argument (eg i32). + +Examples +```rust +#[exhaustive] +trait MyTrait {} + +// ERR: creates infinite implementations for the exhaustive trait. +impl MyTrait for MyType {} + +// OK: trait args are concrete → finite +impl MyTrait for MyType {} + +// OK: trait arg is tied to Self’s generic parameter → +// each concrete MyType has exactly one matching impl +impl MyTrait for MyType {} + +// also OK: still determined by Self +impl MyTrait> for MyType {} +``` + +This makes it impossible for type "T" to implement an infinite amount of `#[exhaustive]` traits, which is what we do not want, since the implementation set of #[exhaustive] traits should be deterministic. + +Because the exhaustive-trait implementation set for the concrete type is deterministic, the compiler/runtime can safely use per-type metadata to answer “does this type implement `Behavior`?” in different crates without coherence surprises + +### Rule 3: Exhaustive traits and all their implementors must be `'static`. + +This gives us the ability to map traits to vtables. (TypeId -> dyn VTable) where `TypeId` represents the `TypeId` of the trait + +if all the rules are satisfied, code that is similar to the code below will be possible + +```rust +#[exhaustive] +trait A { fn a(&self) -> i32; } + +#[exhaustive] +trait B { fn b(&self) -> i32; } + +struct T(i32); + +impl A for T { fn a(&self) -> i32 { self.0 } } +impl B for T { fn b(&self) -> i32 { self.0 * 2 } } + +fn main() { + let t = T(7); + + let a: &dyn A = &t; + let b: &dyn B = a.cross_trait_cast_ref::().unwrap(); // cross-trait cast + + assert_eq!(a.a(), 7); + assert_eq!(b.b(), 14); +} +``` + + + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +### Where are the VTable mappings stored? + +Each type will have an array (`[(TypeId, TraitVTable)]`), where `TypeId` is the `TypeId` of the `dyn Trait`. this is possible because of the `'static only` restriction, and this is similar to how C# does it. + +Essentially, an iteration would be done, until it finds the relevant vtable. If it cannot be found, `None` would be returned. Ofc, this makes it O(n), but C# has a fast path which we could be able to emulate, which I have yet to fully understand. Something we could discuss. + +Inside the vtable of every trait object, a ptr that represents the array can be found. Types that do not have any exhaustive traits implemented, and non static types could simply have a ptr that represents an empty array + +A quick sketch + +``` +struct VTable { + *drop_in_place + usize size; + usize align; + // method fns for TraitX, in trait order + *method1 + *method2 + ... + + // NEW: pointer to exhaustive trait map for concrete T. points to the first implementation map if theres any + ExhaustiveEntry* exhaustive_ptr; + usize exhaustive_len; +}; +``` + +### Intrinsics + +We would have compiler intrinsics that would enable us to get the VTable for a trait object + +```rust +#[rustc_intrinsic] +pub const unsafe fn exhaustive_vtable_of> + ?Sized>( + obj: *const U +) -> Option>; +``` + +where `U` is the trait object, `T` is the trait object we want to cast to. +This intrinsic would be used by a non intrinsic functions to enable safe cross trait casting. Namely `cross_trait_cast_ref` and `cross_trait_cast_mut` + +# Drawbacks +[drawbacks]: #drawbacks + +Some drawbacks would be a slight increase in binary size when using trait objects. + +Even if no type in the program implements an `#[exhaustive]` trait, each vtable of a trait object would still be forced to have a ptr that represents an array of trait implementations, even if it is empty. + +Checking if an underlying type implements a trait would have a time complexity of O(n) in worst case. + +At first, I thought of proposing something similar to a hashmap, but it would be slower than the array version in most cases, and would probably result to even bigger binary sizes + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +### Why this design is best + +Given the rules above, the compiler can build a finite, per-type trait→vtable table that is deterministic across crates. That directly enables cross-trait casting with predictable behavior and no manual bookkeeping. + +It keeps extensibility where it matters: anyone can define an exhaustive trait, and the type’s crate opts in by implementing it once. + +### Other designs considered (and why not) + +Before this, I had another design in mind: The compiler looks at every used type and every used trait object throughout the entire program, and does a many-to-many relationship between the types and traits to figure out whether the types implement the traits and store an `Option` for each relationship somewhere. + +After further thought, this design is either impossible or would require a significant shift in the way the rust compiler works, since each crate is compiled separately and different crates would see a different set of trait implementations. This RFC design works a lot better with the current rust compiler. + + +### Impact of not doing this + +People who need runtime capability checks will keep rebuilding partial solutions (registries, ad-hoc reflection), leading to more boilerplate, more bugs, and less interoperable patterns. + +### Could this be a library/macro instead? + +A library can be used to make this feature possible, like `bevy_reflect` has done, but only by adding extra registration steps. It requires a lot of boilerplate and there could be instances of casts failing despite the type implementing the trait, simply because the dev forgot to register the relationship between the trait and the type + +The proposal reduces maintenance burden by making the relationship “type implements exhaustive trait” automatically discoverable at runtime, without extra code paths to keep in sync. +# Prior art +[prior-art]: #prior-art + +C#, Java, Go, Swift all support “interface/protocol assertions” at runtime. +You can take an erased interface value and ask whether it also implements another interface/protocol, getting either a new interface view or failure. Their runtimes do a conformance lookup and return the right dispatch table (or cached equivalent). + +### Good +Very ergonomic for capability-based code; enables “if it supports X, use X” patterns. + +### Bad +There would be some binary size costs + + +### Rust prior art +Rust stabilized dyn upcasting for subtrait→supertrait only, explicitly not for unrelated traits. + +### Trait registry crates (Rust ecosystem). +Crates like `bevy_reflect` exist to allow this, but they rely on manual/derive registration and can’t be compiler-verified as complete—matching the gap this RFC targets. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- Is `#[exhaustive]` really a good name for these kinds of traits? +- Could `#[exhaustive]` be a `keyword` rather than an attribute +- Is there a more efficient way to map traits to vtables other than using trait TypeIds? +- Would it be possible to make the `#[exhaustive]` trait implementation rules more flexible while preserving soundness? + +# Future possibilities +[future-possibilities]: #future-possibilities + +No additional future possibilities are identified at this time. From be5aff1c55772d871e8017b0d6a808015fa3d2a9 Mon Sep 17 00:00:00 2001 From: theiz Date: Mon, 24 Nov 2025 18:56:05 -0400 Subject: [PATCH 02/14] Set ID, fix formatting --- ...{0000-exhaustive-traits.md => 3885-exhaustive-traits.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename text/{0000-exhaustive-traits.md => 3885-exhaustive-traits.md} (97%) diff --git a/text/0000-exhaustive-traits.md b/text/3885-exhaustive-traits.md similarity index 97% rename from text/0000-exhaustive-traits.md rename to text/3885-exhaustive-traits.md index d2c5facb559..13867d738e5 100644 --- a/text/0000-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -1,7 +1,7 @@ - Feature Name: `exhaustive_traits` - Start Date: 2025-11-24 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) -- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) +- RFC PR: [rust-lang/rfcs#3885](https://github.com/rust-lang/rfcs/pull/3885) +- Rust Issue: [rust-lang/rust#3885](https://github.com/rust-lang/rust/issues/3885) # Summary [summary]: #summary @@ -39,7 +39,7 @@ Making casting between unrelated traits a natural part of the language would mak If a trait is marked `#[exhaustive]`, then the compiler enforces certain rules that make cross trait casting work soundly. -### Rule 1:A crate may only implement an exhaustive trait for types it owns. +### Rule 1 :A crate may only implement an exhaustive trait for types it owns. Equivalently: From 67daf3918d40796d81a40d572e1cd3b6c65c7949 Mon Sep 17 00:00:00 2001 From: theiz Date: Mon, 24 Nov 2025 19:18:01 -0400 Subject: [PATCH 03/14] Fix formatting, again --- text/3885-exhaustive-traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index 13867d738e5..811c0ff0ca2 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -39,7 +39,7 @@ Making casting between unrelated traits a natural part of the language would mak If a trait is marked `#[exhaustive]`, then the compiler enforces certain rules that make cross trait casting work soundly. -### Rule 1 :A crate may only implement an exhaustive trait for types it owns. +### Rule 1: A crate may only implement an exhaustive trait for types it owns. Equivalently: From 415285a14ae938ea13d8e66ef02b8cd64e1f61a2 Mon Sep 17 00:00:00 2001 From: theiz Date: Mon, 24 Nov 2025 19:38:05 -0400 Subject: [PATCH 04/14] Added an 'Exhaustive' trait --- text/3885-exhaustive-traits.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index 811c0ff0ca2..ea6e29c0e00 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -154,8 +154,13 @@ struct VTable { We would have compiler intrinsics that would enable us to get the VTable for a trait object ```rust +// Auto implemented by traits that are exhaustive. Cannot be manually implemented. +pub trait Exhaustive{ + +} + #[rustc_intrinsic] -pub const unsafe fn exhaustive_vtable_of> + ?Sized>( +pub const unsafe fn exhaustive_vtable_of> + ?Sized>( obj: *const U ) -> Option>; ``` From 2bdf71a799ac6d3be957bad7ea488b95883a41ee Mon Sep 17 00:00:00 2001 From: theiz Date: Mon, 24 Nov 2025 19:41:10 -0400 Subject: [PATCH 05/14] Added a 4th rule: "object safe" --- text/3885-exhaustive-traits.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index ea6e29c0e00..0d596598b24 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -91,7 +91,13 @@ Because the exhaustive-trait implementation set for the concrete type is determi ### Rule 3: Exhaustive traits and all their implementors must be `'static`. -This gives us the ability to map traits to vtables. (TypeId -> dyn VTable) where `TypeId` represents the `TypeId` of the trait +This gives us the ability to map traits to vtables. (TypeId -> dyn VTable) where `TypeId` represents the `TypeId` of the trait. + + + +### Rule 4: Exhaustive traits must be object safe + +This is self-explanatory. To be able to store the VTable of an `#[exhaustive]` trait implementation, the `#[exhaustive]` trait would need to be able to have a dyn vtable in the first place. if all the rules are satisfied, code that is similar to the code below will be possible @@ -227,7 +233,7 @@ Crates like `bevy_reflect` exist to allow this, but they rely on manual/derive r [unresolved-questions]: #unresolved-questions - Is `#[exhaustive]` really a good name for these kinds of traits? -- Could `#[exhaustive]` be a `keyword` rather than an attribute +- Could `#[exhaustive]` be a `keyword` rather than an attribute? - Is there a more efficient way to map traits to vtables other than using trait TypeIds? - Would it be possible to make the `#[exhaustive]` trait implementation rules more flexible while preserving soundness? From 10b547e603508dff2255227231d1a823657a7901 Mon Sep 17 00:00:00 2001 From: theiz Date: Mon, 24 Nov 2025 19:46:10 -0400 Subject: [PATCH 06/14] Made the exhaustive marker trait require 'static --- text/3885-exhaustive-traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index 0d596598b24..c9c59ee2fb9 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -161,7 +161,7 @@ We would have compiler intrinsics that would enable us to get the VTable for a t ```rust // Auto implemented by traits that are exhaustive. Cannot be manually implemented. -pub trait Exhaustive{ +pub trait Exhaustive : 'static { } From 704dc9edb51d0f9865ad216928aea468f7061bd9 Mon Sep 17 00:00:00 2001 From: theiz Date: Mon, 24 Nov 2025 19:48:09 -0400 Subject: [PATCH 07/14] adjusted formatting --- text/3885-exhaustive-traits.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index c9c59ee2fb9..4ee4f58d83b 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -93,8 +93,6 @@ Because the exhaustive-trait implementation set for the concrete type is determi This gives us the ability to map traits to vtables. (TypeId -> dyn VTable) where `TypeId` represents the `TypeId` of the trait. - - ### Rule 4: Exhaustive traits must be object safe This is self-explanatory. To be able to store the VTable of an `#[exhaustive]` trait implementation, the `#[exhaustive]` trait would need to be able to have a dyn vtable in the first place. From 765557f70c9e79d719ef2c928de844876b980408 Mon Sep 17 00:00:00 2001 From: theiz Date: Mon, 24 Nov 2025 20:59:36 -0400 Subject: [PATCH 08/14] updated casting methods --- text/3885-exhaustive-traits.md | 55 ++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index 4ee4f58d83b..947e1f0a195 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -17,7 +17,7 @@ trait Behavior { ... } If I have dyn Trait, I want to be able to attempt: ```rust let any : &dyn Any = &MyStruct::new(); -let casted: Option<&dyn Behavior> = any.cross_trait_cast_ref::() +let casted: Option<&dyn Behavior> = cross_trait_cast_ref(any); ``` # Motivation [motivation]: #motivation @@ -85,7 +85,7 @@ impl MyTrait for MyType {} impl MyTrait> for MyType {} ``` -This makes it impossible for type "T" to implement an infinite amount of `#[exhaustive]` traits, which is what we do not want, since the implementation set of #[exhaustive] traits should be deterministic. +This makes it impossible for a type to implement an infinite amount of `#[exhaustive]` traits, which is what we do not want, since the implementation set of #[exhaustive] traits should be deterministic. Because the exhaustive-trait implementation set for the concrete type is deterministic, the compiler/runtime can safely use per-type metadata to answer “does this type implement `Behavior`?” in different crates without coherence surprises @@ -115,7 +115,7 @@ fn main() { let t = T(7); let a: &dyn A = &t; - let b: &dyn B = a.cross_trait_cast_ref::().unwrap(); // cross-trait cast + let b: &dyn B = cross_trait_cast_ref(a).unwrap(); // cross-trait cast assert_eq!(a.a(), 7); assert_eq!(b.b(), 14); @@ -131,7 +131,7 @@ fn main() { Each type will have an array (`[(TypeId, TraitVTable)]`), where `TypeId` is the `TypeId` of the `dyn Trait`. this is possible because of the `'static only` restriction, and this is similar to how C# does it. -Essentially, an iteration would be done, until it finds the relevant vtable. If it cannot be found, `None` would be returned. Ofc, this makes it O(n), but C# has a fast path which we could be able to emulate, which I have yet to fully understand. Something we could discuss. +Essentially, an iteration would be done, until it finds the relevant vtable. If it cannot be found, `None` would be returned. Of course, this makes it O(n), but C# has a fast path which we could be able to emulate, which I have yet to fully understand. Something we could discuss. Inside the vtable of every trait object, a ptr that represents the array can be found. Types that do not have any exhaustive traits implemented, and non static types could simply have a ptr that represents an empty array @@ -158,19 +158,50 @@ struct VTable { We would have compiler intrinsics that would enable us to get the VTable for a trait object ```rust +use core::ptr; + // Auto implemented by traits that are exhaustive. Cannot be manually implemented. -pub trait Exhaustive : 'static { - -} +pub trait Exhaustive: 'static {} #[rustc_intrinsic] -pub const unsafe fn exhaustive_vtable_of> + ?Sized>( - obj: *const U -) -> Option>; +pub const unsafe fn exhaustive_vtable_of< + T: ptr::Pointee> + ?Sized, + U: ptr::Pointee> + Exhaustive + 'static + ?Sized>( + obj: *const T +) -> Option>; +``` + +And then we would use it to implement the functions `cross_trait_cast_ref` and `cross_trait_cast_mut` + +```rust +use core::ptr; + +pub fn cross_trait_cast_ref< + T: ptr::Pointee> + ?Sized, + U: ptr::Pointee> + Exhaustive + 'static + ?Sized> +(obj: &T) -> Option<&U> + +{ + let meta = unsafe { exhaustive_vtable_of::(obj)? }; + let data = obj as *const T as *const (); + let ptr = ptr::from_raw_parts::(data, meta); + Some(unsafe { &*ptr }) +} + +pub fn cross_trait_cast_mut< + T: ptr::Pointee> + ?Sized, + U: ptr::Pointee> + Exhaustive + 'static + ?Sized> +(obj: &mut T) -> Option<&mut U> + +{ + let meta = unsafe { exhaustive_vtable_of::(obj)? }; + let data = obj as *mut T as *mut (); + let ptr = ptr::from_raw_parts_mut::(data, meta); + Some(unsafe { &mut *ptr }) +} ``` -where `U` is the trait object, `T` is the trait object we want to cast to. -This intrinsic would be used by a non intrinsic functions to enable safe cross trait casting. Namely `cross_trait_cast_ref` and `cross_trait_cast_mut` +where `T` is the trait object, `U` is the trait object we want to cast to. # Drawbacks [drawbacks]: #drawbacks From 56fbd91695b9e2cd23db5f615055edf1d497b746 Mon Sep 17 00:00:00 2001 From: theiz Date: Tue, 25 Nov 2025 11:52:20 -0400 Subject: [PATCH 09/14] Added missed 'static bounds --- text/3885-exhaustive-traits.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index 947e1f0a195..fe99457186a 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -165,7 +165,7 @@ pub trait Exhaustive: 'static {} #[rustc_intrinsic] pub const unsafe fn exhaustive_vtable_of< - T: ptr::Pointee> + ?Sized, + T: ptr::Pointee> + 'static + ?Sized, U: ptr::Pointee> + Exhaustive + 'static + ?Sized>( obj: *const T ) -> Option>; @@ -177,7 +177,7 @@ And then we would use it to implement the functions `cross_trait_cast_ref` and ` use core::ptr; pub fn cross_trait_cast_ref< - T: ptr::Pointee> + ?Sized, + T: ptr::Pointee> + 'static + ?Sized, U: ptr::Pointee> + Exhaustive + 'static + ?Sized> (obj: &T) -> Option<&U> @@ -189,7 +189,7 @@ pub fn cross_trait_cast_ref< } pub fn cross_trait_cast_mut< - T: ptr::Pointee> + ?Sized, + T: ptr::Pointee> + 'static + ?Sized, U: ptr::Pointee> + Exhaustive + 'static + ?Sized> (obj: &mut T) -> Option<&mut U> From f95e916e81936453d3a888d1aa8e7f2f28afc7cc Mon Sep 17 00:00:00 2001 From: theiz Date: Tue, 25 Nov 2025 12:12:30 -0400 Subject: [PATCH 10/14] Removed some text --- text/3885-exhaustive-traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index fe99457186a..bdc6d8c83c2 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -133,7 +133,7 @@ Each type will have an array (`[(TypeId, TraitVTable)]`), where `TypeId` is the Essentially, an iteration would be done, until it finds the relevant vtable. If it cannot be found, `None` would be returned. Of course, this makes it O(n), but C# has a fast path which we could be able to emulate, which I have yet to fully understand. Something we could discuss. -Inside the vtable of every trait object, a ptr that represents the array can be found. Types that do not have any exhaustive traits implemented, and non static types could simply have a ptr that represents an empty array +Inside the vtable of every trait object, a ptr that represents the array can be found. A quick sketch From 2882306fc2be51bec62168d263076749b3f8f2d7 Mon Sep 17 00:00:00 2001 From: theiz Date: Tue, 25 Nov 2025 12:34:36 -0400 Subject: [PATCH 11/14] Made the 'static rule more flexible --- text/3885-exhaustive-traits.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index bdc6d8c83c2..b7b9bcce31c 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -89,9 +89,9 @@ This makes it impossible for a type to implement an infinite amount of `#[exhaus Because the exhaustive-trait implementation set for the concrete type is deterministic, the compiler/runtime can safely use per-type metadata to answer “does this type implement `Behavior`?” in different crates without coherence surprises -### Rule 3: Exhaustive traits and all their implementors must be `'static`. +### Rule 3: 'static requirement for cross-trait casting -This gives us the ability to map traits to vtables. (TypeId -> dyn VTable) where `TypeId` represents the `TypeId` of the trait. +Exhaustive traits and their implementors do not have to be 'static in general. However, this cross-trait casting mechanism is only available when both the source and target trait objects are 'static (that is, dyn Trait + 'static, backed by a 'static concrete type). This is required to avoid undefined behaviour. ### Rule 4: Exhaustive traits must be object safe From cd3e0f35ed9b4a9c801f4be4770ebd0741b7065d Mon Sep 17 00:00:00 2001 From: theiz Date: Tue, 25 Nov 2025 12:55:03 -0400 Subject: [PATCH 12/14] Make RFC more consistent --- text/3885-exhaustive-traits.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index b7b9bcce31c..39cdd327f54 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -14,7 +14,7 @@ trait Behavior { ... } ``` -If I have dyn Trait, I want to be able to attempt: +If I have a trait object (for example `&dyn Any`), I want to be able to attempt: ```rust let any : &dyn Any = &MyStruct::new(); let casted: Option<&dyn Behavior> = cross_trait_cast_ref(any); @@ -91,7 +91,7 @@ Because the exhaustive-trait implementation set for the concrete type is determi ### Rule 3: 'static requirement for cross-trait casting -Exhaustive traits and their implementors do not have to be 'static in general. However, this cross-trait casting mechanism is only available when both the source and target trait objects are 'static (that is, dyn Trait + 'static, backed by a 'static concrete type). This is required to avoid undefined behaviour. +Exhaustive traits and their implementors do not have to be 'static in general. However, this cross-trait casting mechanism is only available when both the source and target trait object types are `'static` (that is, dyn Trait + 'static). This is required to avoid undefined behaviour. ### Rule 4: Exhaustive traits must be object safe @@ -129,7 +129,9 @@ fn main() { ### Where are the VTable mappings stored? -Each type will have an array (`[(TypeId, TraitVTable)]`), where `TypeId` is the `TypeId` of the `dyn Trait`. this is possible because of the `'static only` restriction, and this is similar to how C# does it. +Each type will have an array (`[(TypeId, TraitVTable)]`), where `TypeId` is the `TypeId` of the `dyn Trait`. This is similar to how C# does it. + +If either the dyn Trait type or the implementing type is not 'static, the compiler conceptually treats them as 'static when computing the internal (TypeId → TraitVTable) mapping used for cross-trait casting. This is sound because the cross-trait casting operation only works when both the source and target trait object types are `'static` (that is, dyn Trait + 'static). so non 'static variants can never observe or rely on this mapping. Essentially, an iteration would be done, until it finds the relevant vtable. If it cannot be found, `None` would be returned. Of course, this makes it O(n), but C# has a fast path which we could be able to emulate, which I have yet to fully understand. Something we could discuss. From d860c64250543437e912bd533332677202948b49 Mon Sep 17 00:00:00 2001 From: theiz Date: Tue, 25 Nov 2025 12:56:02 -0400 Subject: [PATCH 13/14] Added missed capital letter --- text/3885-exhaustive-traits.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index 39cdd327f54..5a82d476213 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -131,7 +131,7 @@ fn main() { Each type will have an array (`[(TypeId, TraitVTable)]`), where `TypeId` is the `TypeId` of the `dyn Trait`. This is similar to how C# does it. -If either the dyn Trait type or the implementing type is not 'static, the compiler conceptually treats them as 'static when computing the internal (TypeId → TraitVTable) mapping used for cross-trait casting. This is sound because the cross-trait casting operation only works when both the source and target trait object types are `'static` (that is, dyn Trait + 'static). so non 'static variants can never observe or rely on this mapping. +If either the dyn Trait type or the implementing type is not 'static, the compiler conceptually treats them as 'static when computing the internal (TypeId → TraitVTable) mapping used for cross-trait casting. This is sound because the cross-trait casting operation only works when both the source and target trait object types are `'static` (that is, dyn Trait + 'static). So non 'static variants can never observe or rely on this mapping. Essentially, an iteration would be done, until it finds the relevant vtable. If it cannot be found, `None` would be returned. Of course, this makes it O(n), but C# has a fast path which we could be able to emulate, which I have yet to fully understand. Something we could discuss. From 52549854852d6aafecb738ad55338667afa7756c Mon Sep 17 00:00:00 2001 From: theiz Date: Tue, 25 Nov 2025 14:31:43 -0400 Subject: [PATCH 14/14] Removed 'static bound for 'Exhaustive' trait, since now not every 'Exhaustive' trait is static edited comment to be less vague --- text/3885-exhaustive-traits.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3885-exhaustive-traits.md b/text/3885-exhaustive-traits.md index 5a82d476213..216c2e1a20b 100644 --- a/text/3885-exhaustive-traits.md +++ b/text/3885-exhaustive-traits.md @@ -162,8 +162,8 @@ We would have compiler intrinsics that would enable us to get the VTable for a t ```rust use core::ptr; -// Auto implemented by traits that are exhaustive. Cannot be manually implemented. -pub trait Exhaustive: 'static {} +// Auto implemented by 'dyn Trait' types that are exhaustive. Cannot be manually implemented. +pub trait Exhaustive {} #[rustc_intrinsic] pub const unsafe fn exhaustive_vtable_of<