It will enable type pattern matching, which will also enable many other ways of making programs.
Say you are making a game, and your bullet collides with an object. If you want to damage it, you would want to check if the object has a Damageable trait, or if any of its components has it, so you can call its damage method.
But if you were to use a struct “Damageable” as a component, it will need some kind of fn/Fn field that would hold the damage implementation. (Tbh that’s just indirectly using trait objects kinda).
And the struct could hold some data like health that some object may NOT need. Eg a box that does not care about hp. It just needs to be damaged once, and then it’s destroyed. Making the damageable component have HP would be baggage for a box.
There is a reason why bevy_reflect, which is used in the game engine “bevy”, has functionality that enables you to cast between 2 unrelated traits.
But it involves macros and you have to manually register the trait to be able to be casted to, and tell the struct “yeah you can be casted to this trait even if we don’t know your concrete type”
And you also have to register the struct to some kind of registrar that will hold all the vtables or something.
Making casting between unrelated traits a natural part of the language would make this much easier
bevy specifically has a weak preference for manual registration
It should be noted that it's not so much completely "unrelated" traits — the traits are related in that they all provide Castable, similar to a subtrait of Any enabling downcasting to concrete types.
Given const TypeId::of, feature(ptr_metadata), and something like linkme::distributed_slice, which there is reasonable support for std providing a form of (although it's quite likely to be limited to static linking), you can implement the functionality (albeit with linear lookup costs and needing to annotate every impl in addition to the traits). Native support for castable trait impls would use this functionality, but could be stabilized independently of the general support feature.
Sketched impl (won't quite work with linkme as-is due to Details™ but could be made to work):
pub trait DynAny: Any { /* empty */ }
pub trait Castable: Any {
fn impl_table() -> &'static [(TypeId, DynMetadata<Self>)];
// or <Self as Pointee>::Metadata to also support sized downcasts
}
// optional: blanket impl for sized downcasts
impl<T: Sized + Any> Castable for T {
fn impl_table() -> &'static [(TypeId, <Self as Pointee>::Metadata)] {
const { &[(TypeId::of::<Self>(), ())] }
}
}
// downcast and friends
pub fn downcast_ref<T: ?Sized + Castable>(this: &dyn Any) -> Option<&T> {
let (_, meta) = T::impl_table().find(|&(tid, _)| tid == this.id())?;
let ptr = ptr::from_raw_parts(ptr::from_ref(this).cast::<()>(), meta);
Some(unsafe { ptr.as_ref().unwrap_unchecked() })
}
// #[castable] trait Trait { /* … */ }
// expands to unchanged trait def plus:
#[distributed_slice]
pub static Trait: [(TypeId, DynMetadata<dyn Trait>)];
impl Castable for dyn Trait {
fn impl_table() -> &'static [(TypeId, DynMetadata<Self>)] {
DistributedSlice::static_slice(Trait)
}
}
// #[castable] impl Trait for Type { /* … */ }
// expands to unchanged impl plus:
#[distributed_slice(Trait)]
static _: (TypeId, DynMetadata<Self>) = (
TypeId::of::<Type>(),
ptr::metadata(ptr::dangling::<Type>()),
);
Interestingly, this shows we could even attach native support directly into Any without much issue (albeit needing specialization for sized downcasts to avoid the extra costs of fat downcasts), beyond those that are intrinsic to distributed slices.
I don't likeCOM at all, but it is an existing, widely used system that attempts to solve a bunch of closely related problems, so I think anyone who actually wants to implement this (either in a crate or in std) might want to take a really careful look at all the things it does and, in particular, the places where it creates new problems for people. It would be unfortunate if we repeated those mistakes.
I do remember of a crate that used linkme to do something like this.
One of my concerns would be if, a struct implements a trait, and forgets to put the macro to register its implementation, making it unable to be casted to that trait object, from another trait object.