Trait downcasting

Hello, everyone. I am trying to implement something similar to this, but without deprecated modules. I managed to do this
Please help me with this problems;

  1. Am I correct that in convert_to_trait method I do not have any heap allocations? Do i just deconstruct incoming box without drop and feed it to another?
  2. How is it possible to something like this. Basically, I want to cast a box without moving it and get a casted reference to call a function

Your code does not allocate, but does have mistakes.

  1. you should match self.tables.get(&(*something).type_id()) and not match self.tables.get(&something.type_id()) because Box also implements Any, and by default, calling type_id() on it will return the TypeId of the Box, which is not what you want.

  2. In register_type(), you call std::mem::zeroed::<T>(). This is unsound. Not all types have a valid all-zeroes representation. For example, calling register_type::<NonZeroU8>() will be an Undefined Behavior. The correct way to get the VTable of the type is to create a pointer without an instance, for example, with std::ptr::null(). For example:

    fn register_type<T: Any + TestTrait>(&mut self) {
        let pointer = ptr::null::<T>() as *const dyn TestTrait;
        let (_, vtable) = pointer.to_raw_parts();
        let table_ptr = VTablePtr(vtable);
        self.tables.insert(TypeId::of::<T>(), table_ptr);
    }
  1. The following are not really a mistake, but are not good either: In convert_to_trait(), you first call Box::from_raw() then Box::into_raw() on the value. This can lead to double-free if some code in the middle panics. This issue does not exist here, but it's good to avoid it altogether and swap the order, so that at worst you'll have a memory leak but not UB.
  2. The scope of the unsafe blocks is much bigger than needed. It is usually desirable to minimize it. The preferred scope can be sometimes in doubt (like, one can claim that in my playground below I should have put ptr::from_raw_parts_mut() together with Box::from_raw() despite the method being safe, because it's a part of the unsafe transmute). But generally try to narrow their scope.

Playground.

About changing type without moving, you don't need Box here. This can be done with plain references (playground).

2 Likes

But what if I wanted to have a generic VTablePtr in order to have vtable entries for multiple traits? Seems like if my trait is a generic parameter to_raw_part does not return DynMetadata anymore

It's because it's general for any pointer type. You need to constrain Dyn to Pointee<Metadata = DynMetadata<Dyn>>.

How do I force the language to treat my generic parameter as a trait? ptr::null::() as *const dyn Trait cannot be performed because Trait is not a trait

I got your code to compile this way, but haven't checked it for correctness:

#![feature(unsize)]

// ....

    fn register_type<Type: Any>(&mut self) where Type: std::marker::Unsize<Trait>{
        let pointer = ptr::null::<Type>() as *const Trait;
        let (_, vtable) = pointer.to_raw_parts();
        let table_ptr = VTablePtr::<Trait>(vtable);
        self.tables.insert(TypeId::of::<Type>(), table_ptr);
    }
2 Likes

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.