Cross casting of object safe trait objects

@SkiFire13 I see what you did there :slight_smile: Brilliant/Insightful by the way.

Writing this reply so that others can understand the flow of optimization.

  • From my cheap solution with heap allocation and manually written biolerplate code to your solution with heap allocation but removing boilerplate:

    • You templated the trait objects wrappers(struct TraitObjectWrapper<T: ?Sized>) and introduced an additional trait called DynCastExt and provided a blanket implementation for it.
    • You layered the traits such that one(DynCast) is meant for the implementation side(Object) and the other blanket trait(DynCastExt) for the consumption side leading to a better UX like you documented. This split allowed you set the stage for the next set of optimizations: removing the heap allocation and the intermediate wrapper trait objects struct TraitObjectWrapper<T: ?Sized>.
  • Removing heap allocation and trait object wrapper solution:

    • You noticed that the trait object structs/wrappers(struct TraitObjectWrapper<T>) were nothing but an additional indirection that got returned from the internal trait(DynCast) as trait object Option<rc::Rc<dyn any::Any>> which any way got down casted in the blanket impl trait(DynCastExt) to the trait object wrapper struct(struct TraitObjectWrapper<T>).
    • But wait a minute :thinking: : the internal trait(DynCast) and the blanket external trait(DynCastExt) consumed self, hence the intuition to convert the return type of Option<rc::Rc<T>> to an out parameter of &mut dyn any::Any on the internal trait(DynCast).
    • A return type can be made into an out parameter :slight_smile: clever.
    • And since the blanket trait(DynCastExt) depended on the internal trait(DynCast) the call stack was in such a way you could allocate the target conversion Option<rc::RC<T>> on the stack with a default of None and let the internal impl of DynCast fill it out if it were a successful supported conversion. This was aided by the fact that self was consumed in both the internal trait DynCast and external trait DyncCastExt : self: rc::Rc<Self>.
    • You eliminated the tid parameter on the internal trait(DynCast) and swapped it out with the out parameter of &mut dyn Any and this allowed you to call downcast_mut inside the auto generated impl of internal trait DynCast which by the way is the replacement for tid comparison. Why does this work ? This works because the downcast_mut has to take the type of the Option<rc::Rc<dyn $target>>) due to forced type inference.
    • The downcast_mut call is really a conversion between the requested type on DynCastExt which is Option<rc::Rc::<T>> to an Option<rc::Rc<U>> with forced type inference in the auto-generated code impl DynCast by the impl_dyncast macro.
    • The downcast_mut call now becomes a big switch statement that actually checks if the requested type to cast to is supported by the implementation type Object based on the traits registered with it when calling the macro impl_dyncast.
1 Like

@quinedot @bjorn3 @SkiFire13

The solution arrived at however does not solve the situation where blanket implementations are provided for object safe traits across crate boundaries. You still need to register them with the implementation via the impl_dyncast macro call . It is not practically possible to list out the object safe traits implemented due to blanket implementations which is where the rustc should ideally embed the list of object safe traits implemented on a type inside the .rodata section and provide an intrinsic to query that list and cross cast.

For my usecase am ok with listing out the implementations, in general its partly solved problem without embedding of type info inside the binary.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.