Downcasting a Box<Any> into a struct's trait


#1

Hello,

I’m trying to build a piece of code that would do the following

(0) Assuming a struct FooImpl that implements a trait Foo;
(1) create a function that would build a Box (or anything else that is generic and doesn’t contain type information, smart pointer, usize of the memory address, …) from a Box

fn build() -> Box<Any> {
    Box::new(FooImpl{})
}

(2) in the function that called build() convert the Box<Any> into a Box<Foo> (trait object) without having access to the type FooImpl (I’m aiming at separating the implementation and the interface itself)

I’ve tried many different methods but each crash or don’t compile.

I know that you can downcast a Box<Any> into a Box<FooImpl> and then into a Box<Foo> but I need to skip the middle step.

Since you can downcast a Box<Any> into a Box<FooImpl> I’m guessing that the data & vtables of FooImpl are still around, and since one can do something like let x : Box<Foo> = Box::new(FooImpl {}); I’m wondering how I can mimik that logic via something like let x : Box<Foo> = b_any.downcast().unwrap()

Thanks a million


#2

I don’t think that this works. A trait object is not just type information, it is an actual object that happens to live inside a Box. It consists of two pointers: one to the data (the FooImpl object) and one to the v-table of the type (the FooImpl type).

Now to create a trait object for Foo, Rust needs to know from which type the trait object shall be created. Without the type information the compiler cannot infer the position (address in the binary) of FooImpls v-table. When you downcast a Any type to FooImpl you basically hand the compiler that information.

So I don’t think you can skip this step, but I’m not 100% sure. Somehow the compiler must know when a downcast from Any succeeds. This probably happens via the type’s TypeId information (I don’t know exactly), which is probably stored in the Any object. Maybe this could be used to reverse lookup the v-table, but I doubt that this is implemented.


#3

I tried the following on a hunch to check if the v-table within the trait objects might be exchangable.

use std::any::Any;
use std::mem::transmute;

trait Foo {
    fn foo(&self) -> i32;
}

struct Bar(i32);
impl Foo for Bar {
    fn foo(&self) -> i32 { self.0 }
}

fn build(bar: Bar) -> Box<Any> {
    Box::new(bar)
}

fn main() {
    let bar1 = Bar(12);

    let box1 = build(bar1);
    let foo1: Box<Foo> = unsafe { transmute::<Box<Any>, Box<Foo>>(box1) };
    println!("{}", foo1.foo());
}

I’m afraid but the above just produces garbage—which makes sense somehow.

The v-table in a trait object probably only stores the function addresses relevant to the trait, e.g., the address of get_type_id for Box<Any> and foo for Foo—again not 100% sure, but I’d do it that way. So transmuting between two different trait object types does not work.


#4

If you know the intermediate type you’re converting you can do:

let any: Box<Any>;
let typed: Box<MyType> = any.downcast().unwrap();
let tobj = typed as Box<MyTrait>;

#5

I think that’s what @bgbahoue is trying to avoid.


#6

Thanks for your inputs.

@jethrogb yes that’s what I’m trying to avoid indeed since I won’t have the type information where I would need to do the downcast() or I won’t know what generic signature I would need to use in the build() method

@mindsbackyard thanks for the hunch. I tried that one indeed and it cashed (with no error message actually which is weird too).
I had roughly the same reasoning for the vtable. To go further down that route do you know how one can access the vtable of an object ?
I looked into the raw::TraitObject but I couldn’t even create one with the transmute method suggested by the doc. Anyone ever used a TraitObject ?

I have found a crate that gives access to the data part of a TraitObject but couldn’t find something on the vtable …


#7

Why don’t you just return Box<Foo> from your build()?


#8

That’s exactly right - a trait object is a fat pointer consisting of pointer to the vtbl of that trait and a pointer to the data of the object. The type is otherwise erased, and that’s why you can’t cast to another trait object.


#9

Do you need to start with Any or can you use your own trait? If you can start with your own trait, you can create “landing pads” to trampoline from one trait object to another. For example:

trait MyAny {
  fn as_foo(&self) -> Option<&Foo>;
  fn as_bar(&self) -> Option<&Bar>;
}

trait Foo {}
trait Bar {}

struct S;

impl Foo for S {}
impl Bar for S {}

impl MyAny for S {
   fn as_foo(&self) -> Option<&Foo> {
        Some(self)
   }

   // similar for as_bar
}

#10

Because in the build() method I don’t have the information on the interface (trait) implemented by FooImpl (it’s within code generated by a procedural macro)


#11

I can use anything that is generic and could work for every possible struct.

It will return a Box of a concrete struct but it could be any struct.

That’s why I chose Any at first


#12

I would try to go in the direction @vitalyd proposed. If all your types which you need to store gernerically implement MyAny then you can just store Box<MyAny> and use the conversion methods to get the appropriate trait. You can even do this to ease the implementation:

trait Foo {
    fn foo(&self) -> String;
}

trait MyAny {
    fn as_foo(&self) -> &Foo;
}

impl<T: Foo> MyAny for T {
    fn as_foo(&self) -> &Foo { self }
}


struct Bar(i32);
impl Foo for Bar {
    fn foo(&self) -> String { format!("I'm Bar {}", self.0) }
}

struct Baz(f64);
impl Foo for Baz {
    fn foo(&self) -> String { format!("I'm two times Baz {}", 2 * self.0) }
}

fn main() {
    let bar = Bar(12);
    let baz = Baz(23.0);
    let box1: Box<MyAny> = Box::new(bar);
    let box2: Box<MyAny> = Box::new(baz);

    println!("{}", box1.as_foo().foo());
    println!("{}", box2.as_foo().foo());
}

#13

Also because I was wondering about boxed trait conversion, I did something stupid which I would strictly advise against as it has no benefit over the MyAny approach. Just for the sake of completeness here is how to build a reverse lookup for v-tables:

#![feature(raw)]
#![feature(get_type_id)]
use std::any::{Any, TypeId};
use std::mem;
use std::collections::HashMap;
use std::raw;

#[derive(Clone,Copy)]
struct VTablePtr(*mut ());
pub struct FooVTableLookup {
    vtables_by_type: HashMap<TypeId, VTablePtr>
}

impl FooVTableLookup {
    fn new() -> Self {
        Self { vtables_by_type: HashMap::new() }
    }

    fn register_type<T: Any + Foo>(&mut self) {
        let vtable_ptr = unsafe {
            let void: T = mem::zeroed::<T>();
            let foo_trait: &Foo = &void;
            let raw_foo_trait: raw::TraitObject = mem::transmute(foo_trait);
            VTablePtr(raw_foo_trait.vtable.clone())
        };

        self.vtables_by_type.insert(TypeId::of::<T>(), vtable_ptr);
    }

    fn convert_to_trait(&self, something: Box<Any>) -> Box<Foo> {
        match self.vtables_by_type.get(&(*something).get_type_id()) {
            Some(&vtable_ptr) => unsafe {
                let raw_something: raw::TraitObject = mem::transmute(something);

                let foo_trait: Box<Foo> = mem::zeroed();
                let mut raw_foo_trait: raw::TraitObject = mem::transmute(foo_trait);

                raw_foo_trait.data = raw_something.data;
                raw_foo_trait.vtable = vtable_ptr.0;

                mem::transmute(raw_foo_trait)
            },
            None => panic!("Unknown type")
        }
    }
}

trait Foo {
    fn foo(&self) -> String;
}

struct Bar(i32);
impl Foo for Bar {
    fn foo(&self) -> String { format!("I'm Bar {}", self.0) }
}

struct Baz(f64);
impl Foo for Baz {
    fn foo(&self) -> String { format!("I'm two times Baz {}", 2 * self.0) }
}


fn main() {
    let bar = Bar(12);
    let baz = Baz(23.0);
    let box1 = Box::new(bar);
    let box2 = Box::new(baz);

    let mut lookup = FooVTableLookup::new();
    lookup.register_type::<Bar>();
    lookup.register_type::<Baz>();

    println!("{}", lookup.convert_to_trait(box1).foo());
    println!("{}", lookup.convert_to_trait(box2).foo());
}

You could basically extend this to store arbitrary v-tables (not only Foo) by identifying the trait via a the TypeId of an according boxed trait object.


#14

First thing is to consider if your making correct approach to problem. Any and downcasting are hacky ways to use the language and better to avoid if you can.

There is no inheritance for traits; Types implement a flat list of traits. So what most describe is really a side-cast/cross-cast.

@vitalyd code can also be extended to mut and move

fn as_foo_mut(&self) -> Option<&mut Foo>;
fn into_foo(self: Box<Self>) -> Result<Box<Foo>, Box<MyAny>>;

#15

It looks like the query_interface crate may be of use to you?

The problem here is that it’s impossible for the rust compiler to generate an implementation for every possible conversion between trait objects, because it would need to know the concrete type to pick the correct vtable, and the set of types implementing a trait is unbounded. This is why you cannot convert between trait objects.

query_interface solves this by requiring you to annotate your types with a set of traits to which it can be converted. In this way it works like interfaces in other languages.

Of course, in general, it’s better if you can solve this a more rustic way, but query_interface is there as a backup.


#16

Thanks all for the pointer and the help.

@mindsbackyard thanks for yor experimentation on the vtables. I agree it’s a dangerous yet rawly elegant :slight_smile:

I’ll look into the query_interface crate but as per @jonh’s comment I guess I’ll have to find a work around if I really want a stable implementation of that feature.


#17

@mindsbackyard I just stumbled across this thread and, in a sick kind of way, your TraitObject transmute reverse lookup table is genius!


#18

Thanks, I’ll take that as a compliment :wink: