Since trait upcasting is now stable, I think it now makes sense to start thinking about casting between two unrelated traits.
How would the compiler enable this to happen?
There will be a trait, let's call it Castable
. One of it's roles will be to serve as a marker to indicate that it can be used to cast to another trait. There will be a single method, which will look like this
trait Castable {
fn get_vtable(&self, dyn_trait_type_id: TypeId) -> Option<&'static ()>;
}
This method's implementation would be generated by the compiler. it would return the vtable to the specified dyn, EG
some_struct.get_vtable(TypeId::of::<dyn Any>())
gets the vtable of dyn Any for type some_struct
How will the compiler generate this method to get all the types that would be casted to?
the compiler will ONLY keep track of all dyn Trait
s that supertraits Castable, that have been used in the program
trait SomeTrait : Castable{}
let foo : &dyn SomeTrait;
now the compiler will ONLY take note of dyn SomeTrait
, because its the only dyn Trait
used somewhere in the program. this way, the compiler will not have to look for how many possible dyn Foo<T>
exists, where T
could be anything
for every struct that implements SomeTrait, the compiler will include it in their get_vtables method
impl SomeTrait for i32{}
Castable
for i32
would look like
impl Castable for i32{
fn get_vtable(&self, dyn_trait_type_id: TypeId) -> Option<&'static ()>{
if dyn_trait_type_id == TypeId::of::<dyn SomeTrait>(){
return Some(/**compiler returns the vtable somehow*/);
}
return None
}
}
with that, rusts std could now make a method that would look like this
fn cast<T: Castable + ?Sized + 'static>(input: &impl Castable + ?Sized)
-> Option<&T>;
and use the vtable to return the appropriate cast
Possible issues
Some ways of implementing traits that supertrait Castable
could cause issues. for example
trait MyTrait : Castable {}
struct Wrap<T>(T);
impl<T> MyTrait for T where Wrap<T>: MyTrait {}
and then, calling a method like this
fn require_trait(_: impl MyTrait) {}
fn main() {
require_trait(1i32);
}
would result in an infinite recursion, making the compiler hit the recursion limit.
if a trait that can be castable, in this case, MyTrait
, were to be implemented that way, merely upcasting a struct to dyn Castable
, or any other trait that supertraits it, would cause the compiler to do the thing that makes it hit the recursion limit, since it would have to check if the struct implements Wrap<Self>
to see if it can implement MyTrait(hope im making sense)
This isn't good, because if a crate dependency were to implement a trait that supertraits Castable
this way, upcasting structs to dyn Castable would be an automatic error.
How to prevent this?
By making the compiler reject where clauses when implementing a trait that supertraits Castable, so it wont be able to happen in the first place.
so merely doing
impl<T> MyTrait for T where Wrap<T>: MyTrait {}
would cause the rust compiler to give a compiler error, saying something like:
Cannot add where clause when implementing MyTrait
, since it supertraits Castable
This prevents the recursion thing from happening when casting to dyn Castable
(or any of its subtraits), since you cannot even implement the trait
that way without the Where clause in the first place.
If there is any possible situation that this could mess up the compiler, feel free to comment! (and ill try to provide a solution)