Convert Box<dyn T> to Box<dyn Any>

I have a trait object for trait T and want to use the downcast-functionality of Box<dyn Any>. However, I am unable to convert Box<dyn T> to Box<dyn Any>.

Playground
Error:
error[E0605]: non-primitive cast: std::boxed::Box<dyn T> as std::boxed::Box<(dyn std::any::Any + 'static)>

Is there any way to perform this cast?

No, you can’t

Does the crate downcast-rs help with anything?
https://docs.rs/downcast-rs/1.0.4/downcast_rs/index.html

1 Like

Yeah, that would work. But only if you are going to use references and Box.

If you control T, you can put a little manual trampoline to do the conversion.

3 Likes

Is there any reason this is not possible out-of-the-box?

It has to do with the undefined layout of multi trait objects, which is necessary to enable these coercions. There are some complications around dylibs that use trait objects.

Also, it’s because the Any implementation for each type is different, but when you use trait objects you erase the type, meaning that you can no longer know what the original type is and therefore lose the implementation for Any appropriate for the type that is erased.

Also Any requires Self: 'static which might not be satisfied by dyn T

In what sense do I erase the type when using trait objects? As I understood, trait objects preserve the type at runtime.

No, the types get erased. It isn’t possible to recover the types at runtime. Any uses TypeId which is compiler magic to do what it does. Technically Any is unsound because TypeId uses hashing internally, and because hash collisions are a thing, this could be used to transmute between types using Any. But this is unlikely and highly unstable so it seems fine (although there was some discussion on fixing this, I don’t remember where it was). But even with Any you can’t figure out the type inside the trait object, you have to know what type is inside to do anything with Any.

For example

fn foo(x: &dyn Any) {}

What is the type of x behind the trait object? It is impossible to tell.

1 Like

They do preserve the type at runtime but they also don’t, there isn’t a marker of sorts that ids the type of the object behind a pointer, which is what half of the trait object is.
Trait objects are similar to this:

let trait_obj: (*const (), *const ());
//where trait_obj.0 is the data
//And trait_obj.1 is the pointer to the vtable (Function pointers, destructor, size, etc)

All that trait objects guarantee is that the data behind trait_obj.0, when passed to functions under trait_obj.1's function pointers will run the respective functions. For example, if you’ve got dyn Any the vtable is laid out like this:

struct VTable {
    dtor: fn(*const ()),
    size: usize,
    align: usize
    type_id: fn(*const ()) -> TypeId
}

And it depends on the specific type, what VTable we get for each type, so we’d need to know what type is under the data pointer to assign an appropriate VTable at runtime, which isn’t available.

2 Likes

I am confused. Clearly, some type-information is required for working with trait objects (VTable stuff). Why does the vtable not contain the typeid itself that would allow for arbitrary downcasting?

I don’t understand. On &dyn Any I can use downcast_ref, which gives me references to the concrete type if I “guessed” correctly. Of course, this happens at runtime

The very fact that you have to guess means that you don’t know what’s inside. Thus showing my point.

There is just enough type information to call trait methods and to clean up the value, but not enough to actually use the type itself at runtime. This information is injected into the trait object when the type is coerced to a trait object.

You can’t do arbitrary downcasting because Tust needs to know type information at compile time to allocate stack space and check your code, but that type info is only known at runtime.

@power-fungus you may also look at this other thread, where you can see an example of reconstructing a trait object with an overridden vtable, to achieve upcasting one trait object into another.

Most techniques currently need what @vitalyd called a trampoline, to ensure that type-specific information is accessible from within the vtable of a type-erased object (this is, for instance, what ::downcast-rs does).

I understand that this is the case. However, what is the reason to exlude the type-id from the vtable?

The thread linked above gives another use case:

trait A {}
trait B: A {}

fn upcast(x: Box<dyn B>) -> Box<dyn A> {
    panic!("this is not implementable??")
}

For me, the functionality of upcast is useful in some cases. It is frustrating to me that I am unable to comprehend why this is not possible to implement: All information should be there to construct a trait-object of type A from one of type B.

Because the typeid is useless to get the actual type, i.e. you can’t go from a typeid to a type. It is a hash, so if two types are equal, then their typeids are also equal, but not the other way around. Any uses the fact that the hash is good to go from typeid to type, not really. It still needs you to tell it which type is inside. For example,

fn get(x: &dyn Any) -> &??? {
    magic_on_any(x)
}

How would this function’s signature be written? What is the return type? This function is the one you proposed that would use some information inside the trait object to convert the trait object to a concrete type.

There is a reason why you can only as Any is this the type inside you via Any::is::<T>(), but to ask what type is inside.

This is possible to implement, but the Rust team has deliberately not implemented it because there are some important edge cases to handle. For example, imagine this

/// In crate Foo

trait Foo {
    fn foo(&self) {}
    fn qaz(&self) {}
}

/// In crate Bar,

trait Bar: Foo {
    fn bar(&self) {}
    fn hal(&self) {}
}

struct MyType;

impl Foo for MyType {}
impl Bar for MyType {}

The layout of a trait object is currently

struct TraitObject {
    ptr: *const (),
    vtable: *const ()
}

Now, Foo and Bar's vtable looks something like

struct FooVtable {
    qaz: fn(*const()),
    foo: fn(*const()),
    ...
}
struct BarVtable {
    hal: fn(*const()),,
    qaz: fn(*const())
    bar: fn(*const()),
    foo: fn(*const()),
    ...
}

I have snipped out the unnecessary details. Now we need a way to convert from BarVTable to FooVtable. In this case, we could just copy over the qaz and foo functions and be done.

But what if we want to support multi-trait objects? (dyn Foo + Bar + Any + Debug + ...). Then we would want to be able to convert to any sub-set of trait objects right? (dyn Foo + Any + Debug). If so, we have a combinatorial explosion of required conversions, but this is very bad for dylibs because they must provide every combination of traits that they can, which is unsustainable. The other way to solve this problem would be to make pointers to trait objects super-fat, i.e.

struct TraitObject {
    ptr: *const (),
    vtables: [*const (); N]
}

where N is the number of super traits or traits in the multi-trait object. But this has a huge penalty to micro controllers and other devices that are memory constrained.

So as you can see the problem is quite a bit more complicated than at first glance.

2 Likes

Your explanation does make sense. Thank you!

It does exist already and is no invention by me. downcast_ref is the function that would do that.

pub fn downcast_ref<T>(&self) -> Option<&T> 

No, that is a different function, it has a generic type that the caller must supply. Not something that the compiler can infer based on the trait object alone.

Basically, if the caller must supply the type, then you are not using the information inside the trait object to do decide what type to cast to.

downcast has to inspect the type-information of the &dyn Any trait object in order to decide whether to return Some or None. This is the mechanism I was referring to.

1 Like

This is a compiler-built function which returns a u64 identifying the type. It’s a hash, as @KrishnaSannasi said, and you can’t get the input from a hash, meaning that this can only be used to compare equality of types, given a TypeId and a type.


P.S. if it wasn’t implicit, TypeId is a transparent wrapper around u64