Downcast not from Any, but from any trait

Currently Rc::downcast::<T>(...) only accepts Rc<Any>. Could it be modified to accept any covariant trait?

1 Like

Assuming I correctly understand what you’re asking for, the answer is no. Not in any straightforward way. Rust is statically types, and you cannot test whether or not a type implements a trait at runtime, unless you make sure this information is made available somehow. The Any trait only offers TypeId which allows comparing individual types for whether or not they are equal, but nothing more. There’s no information about what traits a type implements in a TypeId.

If you need such downcasting ability in a trait hierarchy, you could consider adding methods such as fn downcast_ref_to_foo(&self) -> Option<&dyn Foo> (and potentially more for &mut self or Box<Self> downcassting, etc.) to a supertrait of Foo that you’d like to downcast from, and you’ll have to make sure to accurately implement that yourself in order to reflect whether or not Foo is implemented by a type.

5 Likes

On a second read, you might be asking for something different here, and my previous answer is perhaps what you expected in this following thread, (without explicitly asking for it)


Downcasting is implemented in terms of a method to retrieve a TypeId, and this is what Any provides. Downcasting to a type from Rc<dyn SomeOtherTrait> can’t work for just any other abitrary trait, but if you set up the trait accordingly, it could be feasible. In fact, it is possible to implement other traits that include Any-like capabilities already: AFAIR crates like mopa or downcast-rs present patterns of how to achieve this.

1 Like

In this case I'll prefer to not downcast then... I'll just use Rc<dyn Any> instead of Rc<dyn DisplayObject> wherever needed.

What you're probably looking for is something like

3 Likes

(NOT A CONTRIBUTION)

It would be trivial to make this something that Rust had out of the box instead of making traits opt into this with "mopa" style hacks.

All you'd need is to include the type id in every vtable just like the size and align already are, and then add a size_of_val/align_of_val type of function (TypeId::of_val?) to access that type id for anything that is 'static. Then you could use that to implement downcast on pointers to any trait object. You don't have to restrict it to trait objects, but its useless for anything else, and you could restrict it to trait objects with the Pointee/Metadata/DynMetadata APIs that have already been added.

I had to do the "mopa" trick myself just a few weeks ago. This is the kind of incremental improvement that I wish the project would focus on to make trait objects easier to deal with.

12 Likes

But this isn't zero cost. This grows every vtable by a TypeId (two u64s at the moment IIRC). If you want to do that, you can do that the way Any does.

What I do want to see is some feature to enable dyn Trait with const. This will enable more efficient Any-like conversion (instead of a method, just retrieve a constant). However I'm not sure what shape the feature should have.

1 Like

In my ideal world I should be able to change a Rc<dyn Foo> in a Rc<dyn Foo + Any> and then being able to use Rc::downcast without additional hassles thanks to trait upcasting automatically coercing the Rc<dyn Foo + Any> to a Rc<dyn Any>.

3 Likes

(NOT A CONTRIBUTION).

A type id is one u64; this is a really stretched definition of "not zero cost" when every vtable already contains a constant overhead of 3 usizes (size, align and drop in place) plus a usize for every function in the trait definition.

Supporting proper upcasting is another solution; the key difference is that it if you get a trait object from a library you can't possibly downcast it unless the library has added the Any bound, whereas it could also just be a feature of trait objects in general.

But the current solution - requiring the trait definer to know about and use a library like mopa or write unsafe code and understand how downcasting is implemented - is really inadequate. The expertise needed to write a downcastable trait object that isn't Any is far too high.

9 Likes

TypeId being a u64 isn't set in stone; this PR changes it to be a u64 plus a static reference (and a larger static memory footprint) for example.

1 Like

I was referring to this:

And this may be important to some people.

Notably every trait being Any means that the static data cost of TypeId (a mangled type name string, or perhaps a cryptographic-strong hash of such instead) is paid for every type you take any dyn rather than just the ones you make dyn Any.

I fully agree that mopa should be easier to do — it should just be trait Mine: Any — but since it's required that the RTTI is more than just a u64 (see discussion about how TypeId is unsound; we do have collisions in practice), it makes sense to keep it opt in.

3 Likes

I don't do RTTI often so I'm probably missing the context here. Why doesn't trait Mine: Any just work? I looked at the implementation of mopa crate and it seems to replace Any with trait mopa::Any: Any with TypeId::of as a trait method. Is that the issue? It seems like a simple fix...

AFAICT, the mopa::Any trait only exists because Any::type_id was (differently named and) unstable at the time when mopa was released / last updated.


The problem why you can’t downcast a trait Mine: Any is because the downcast… methods are inherent methods of dyn Any (with &self or &mut self receiver) or Rc<dyn Any> or Box<dyn Any>, etc.

The macro from mopa adds analogous methods for &dyn Mine, &mut dyn Mine, or Box<dyn Mine>. There is also ongoing development on trait upcasting, for turning &dyn Mine into &dyn Any (or similarly with &mut … or Box<…>, etc.) between sub-/super-traits trait Mine: Any, so you could cast &dyn Mine to &dyn Any first and then use regular downcast_ref. This however doesn’t give the perfect API for something owned like Box<dyn Mine> because with this approach, you’ll be left with a Box<dyn Any> in case downcasting fails, with no way to turn it back into Box<dyn Mine>, whereas mopa's downcast will return Result<Box<T>, Box<dyn Mine>>.

2 Likes