Skip to content

Comment on Rust Any part 3: we have upcasts

Comments

This example shows how it works for one trait, Debug, but what if you have a type that (might) implement multiple traits A, B, and/or C? It isn't clear to me if that is possible, unless the type implements all of those traits. What I'd like to do is have some base trait object and query it to see if it supports other interfaces, but also not have to have stub "I don't actually implement this" trait impls for the unsupported ones. A bit like how I might use dynamic_cast in c++.

(I believe I understand that in rust this has not historically been possible because rust doesn't have inheritance, so, there can be only one vtable for a type, rather than an chain like you might have in c++.)

This example shows how it works for one trait, Debug, but what if you have a type that (might) implement multiple traits A, B, and/or C? It isn't clear to me if that is possible, unless the type implements all of those traits.

Yeah, you can do the same trick if you care about types that implement all of A, B and C.

What I'd like to do is have some base trait object and query it to see if it supports other interfaces, but also not have to have stub "I don't actually implement this" trait impls for the unsupported ones.

Currently this is not possible, though it might be possible in the future once Rust gets specialization. With that you would basically be able to write the "I don't actually implement this" stub automatically. The catch however would be that this can only work for `'static` types, since such specialization for non-`'static` types is unsound.

I believe I understand that in rust this has not historically been possible because rust doesn't have inheritance, so, there can be only one vtable for a type, rather than an chain like you might have in c++.

The issue is kinda the opposite. In Rust types can implement any number of traits (even infinite!), so they would require an potentially an infinite amount of vtables if implemented like in C++, which is just not possible. So instead this is splitted from types and moved to trait objects, which allow to carry a vtable for one specific trait.

I think you’ve just confused how traits are used. They’re more like Java interfaces and there’s no inheritance - if you have trait A: B it means whatever type implements a also separately and explicitly has to implement B. Multiple traits would work similarly - either the downcast would work if the type implements a trait or you’d get back a None when you try to downcast.

You can do something akin to QueryInterface from COM, though it's a bit verbose.

https://play.rust-lang.org/?version=beta&mode=debug&edition=... (ninja edited a few times with some improvements)

With a bit of API massaging, this could be improved quite a bit in terms of ergonomics. The challenge is that traits often require concrete structs if you want them to participate in dynamic typing.

Basically you can now make something like `Arc<dyn QueryInterface>` work, where multiple traits are available through a `query_interface`:

    fn main() {
        let x = Arc::new(X);
        let y = Arc::new(Y);
        let any = [x as Arc<dyn QueryInterface>, y as _];
        for any in any {
            if let Some(a) = any.query_interface::<dyn A>() {
                a.a();
            }
            if let Some(b) = any.query_interface::<dyn B>() {
                b.b();
            }
        }
    }

the article isn't very good for anyone not already familiar with the problem

What I'd like to do is have some base trait object and query it to see if it supports other interfaces
rust doesn't have inheritance

rust traits and lifetimes have inheritance, kinda, (through rust types do not)

E.g. `trait A: Debug + Any` means anything implementing A also implements Debug and Any. This is a form of interference, but different to C++ inheritance.

This is why we speaking about upcasts when casting `&dyn A as &dyn Debug` and downcast when trying to turn a `&dyn A` into a specific type.

But because this inheritance only is about traits and the only runtime type identifying information rust has is the `Any::type_id()` the only dynamic cast related querying you can do is downcasting. (Upcasting is static and possible due to changes to the vtable which allow you to turn a `&dyn A` vtable into a `dyn Any`/`dyn Debug` vtable (in the example below).

The part of bout downcast form the article you can ignore, it's some nice convenient thing implicitly enabled by the upcasting feature due to how `downcast_ref` works.

This https://play.rust-lang.org/?version=beta&mode=debug&edition=... might help.

So I think what you want might not be supported. It also is very hard to make it work in rust as you would conceptually need a metadata table for each type with pointer to a `dyn` vtable for each object safe trait the type implements and then link it in every vtable. The issue is concrete rust traits can be unbound e.g. a type `C` might implement `T<bool>` and `T<T<bool>>` and `T<T<T<bool>>>` up to infinity :=) So as long as you don't have very clever pruning optimizations that isn't very practical and in general adds a hidden runtime cost in ways rust doesn't like. Like e.g. if we look at `dyn T` it e.g. also only contains runtime type information for up casting if `T: SomeTrait` and downcasting if `T: Any` and the reason up casting took like 5+ years is because there are many subtle overhead trait offs between various vtable representations for `C: A+B` which might also affect future features etc.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.