Run-time Dynamic type-casting (RTTI)

Seems to me the OP wants some sort of reflection system like the following.

use std::any::Any;

type Symbol = u32;

trait Struct {
    fn get(&self, field: Symbol) -> Option<&dyn Any>;
}

trait Object {
    fn downcast(&self, typ: &str) -> Option<&dyn Struct>;
}

struct MyStruct {item: i32}
const ITEM: Symbol = 0;

impl Struct for MyStruct {
    fn get(&self, field: Symbol) -> Option<&dyn Any> {
        if field == ITEM {Some(&self.item)} else {None}
    }
}

impl Object for MyStruct {
    fn downcast(&self, typ: &str) -> Option<&dyn Struct> {
        if typ == "MyStruct" {Some(self)} else {None}
    }
}

fn main() {
    let ms = MyStruct {item: 1};
    let ptr: &dyn Object = &ms;
    let type_name = "MyStruct";
    let item = ptr.downcast(type_name).unwrap().get(ITEM).unwrap();
    assert_eq!(1, *item.downcast_ref::<i32>().unwrap());
}
2 Likes

hm, it is interesting ..

Please understand that your idea is not a bad idea in the abstract. The problem is only that it's not easily compatible with other design decisions already made in Rust. So many other things about Rust would have to change, to make this a feature that felt well-integrated, that it would effectively be a different language.

15 Likes

ok, I understand, no problem. It was "question" only .,..

Hi, I have hitting that issue as well (especially when working on shakacode/messagebus). Here is how it may look like (al least on my opinion): TraitObject has vtable for each type's impl - we can add typeid and next_sibling/prev_sibling to this vtable impl block. It will be possible to iterate through all dyn implementations for given type. And dynamic_cast function may look like (just draft):

pub unsafe fn dynamic_cast_ref<T: ?Sized + Unsized, U: ?Sized + Unsized>(tp: &T) -> Option<&U> {
    let target = TypeId::of::<U>();
    let to: TraitObject = mem::transmute(tp);
    let mut v = to.vtable;
    loop {
        let td: Option<&DynDeclType> = mem::transmute(v.offset(NEXT_SIBLING_OFFSET));
        let td = if let Some(val) = td {
            val
        } else {
            break;
        };

        if td.type_id == target {
            return Some(mem::transmute(TraitObject {data: to.data, vtable: td.vtable});
        } else {
            v = td.vtable;
        }
    }

    // and same for prev_sibling as well

    None
}

it is interesting. I can not estimate how difficult it will be when it will be included into RUST.... it is up to you...

This would work if all code would be compiled at once. How would it work with rust's compulation model where each crate is compiled individually? We don't have control over the linker to add a way to implement next_sibling/prev_sibling across compilation units. Even if we could, that would require making all vtables exported, which would cause symbol conflicts due to the fact that vtables are copied once for every compilation unit that uses them.

Make sense. What about to add some optimization layer before linker (linker wrapper kind of)? It will allow access to compilation units and it will be possible to do necessary rust-related optimization before pass it to ld.

There is no guarantee that rustc is the one invoking the linker. With for example --crate-type staticlib or --emit obj, it is possible for an existing C build system to be the one invoking the linker.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.