Casting between trait object types

I'm currently writing a really stupid project (write a python AST in json and this magical transpiler thingy majigy will turn it into python, what I'm writing), and I need to cast between different trait objects.

I currently have a Codegen trait that defines a code generation function, but I also have another ContainsBlock trait that extends then Codegen trait that defines a getter and a setter for how many indents deep the current code block is. I need to cast from a Box<dyn Codegen> into a Box<dyn ContainsBlock>, and so far I have not found a way of casting from the supertrait to the subtrait without it failing at runtime (Talking about std::any::Any, downcast_ref::<Box<dyn ContainsBlock>>() doesnt work).

Any ideas on how I can do that, or other ways I could implement this?

Rust doesn't support this. This is precisely why I try to discourage people from thinking of traits as like interfaces from Java or C#. (There's an RFC for this specific case (RFC 3324), but that hasn't been stabilised yet.)

About Any: downcasting only works to the single, specific concrete type that the dyn Any was created from. You can't downcast to trait objects, even if the underlying type implements those traits. The information needed for this to work simply doesn't exist at runtime.

The way I've solved this in the past is to ensure that any trait objects I need to cast have methods specifically for doing the cast manually. For example, let's say I have a collection of dyn Animals, and I need to be able to sometimes cast them to other trait objects. The trait might end up looking like this:

trait Animal: Object + fmt::Debug + Food {
    // The functionality of `Animal`.
    fn make_noise(&self);

    // Trait casts.
    fn as_debug(&self) -> &fmt::Debug;
    fn as_display(&self) -> Option<&fmt::Display>;
    fn as_object(&self) -> &dyn Object;
    fn as_pet(&self) -> Option<&dyn Pet>;

    // Maybe you need to do this, too?
    fn into_food(self: Box<Self>) -> Box<dyn Food>;
}

The implementation of those as_* and into_* methods are usually trivially simple, but you do need to do them for every type that implements Animal.

There might be a good crate that can automate this to some degree, but I'm not personally aware of one.

2 Likes

Every problem can be solved with one more level of indirection! Just add a supertrait and a blanket impl for automatically implementing the as_trait_object() methods: Playground.

trait AsDebug: Debug {
    fn as_debug(&self) -> &dyn Debug;
}

impl<T: Debug> AsDebug for T {
    fn as_debug(&self) -> &dyn Debug {
        self
    }
}

trait Animal: AsDebug {
    fn make_noise(&self);
}

This hinges on two features:

  1. The blanket impl's generic type parameter implies a Sized bound that makes the unsized coercion work
  2. Supertraits' methods are automatically available in subtraits (and this is true for trait objects, too).
2 Likes

Here's a version with Boxes. I'm not convinced it's the best way, and hopefully I correctly avoided nested boxing and the like correctly. (Not tested beyond what you see.)

Related:

I do know about upcasting, but is there a way to downcast from a supertrait to a subtrait? that's more important in my case.

Not in general; that probably only makes sense in the context of the concrete types anyway, e.g. Playground.

1 Like

Unconditionally obviously not, but see my playground.

The core concept is getting to the compiler to implicitly and infallibly downcast to the concrete base type (as part of dynamic dispatch of self: Box<dyn Trait>) at which point its upcasting implementation will be called.

1 Like

seems like my implementation is just bad then. I'll just redo the implementation and try to avoid the need for up/downcasting.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.