Downcasting to a trait?

There are crates that enable downcasting to traits but I wanted something more lightweight and tried this:

pub trait Downcast {
    fn to_block(self: Box<Self>) -> Box<dyn Block> {
        panic!()
    }
    fn to_inline(self: Box<Self>) -> Box<dyn Inline> {
        panic!()
    }
}
impl<T: Block> Downcast for T {
    fn to_block(self: Box<Self>) -> Box<dyn Block> {
        self
    }
}
// ERROR: conflicting implementations of trait `Downcast`
impl<T: Inline> Downcast for T {
    fn to_inline(self: Box<Self>) -> Box<dyn Inline> {
        self
    }
}

Is there any way to make this work? My current workaround is to implement Downcast for every concrete type.

No, it can't work. There can be only one implementation of a trait for a type, but a type can implement many traits at the same time — in your case both Inline and Block. You probably mean it will implement either one or the other, but Rust doesn't know that, and there's no way to express that.

You'll probably need to have specific implementations. You can use macros to avoid boilerplate.

BTW: it's a bit weird to panic! in the unimplemented method, because that requires already knowing which one you'll get. It's not abstract then! Why not implement code to work on T: Block if you know it's block, and Inline if you know to_inline will succeed?

If you have T: Block, then you don't need a trait to make Box<&dyn Block>. Box::new(t) as Box<&dyn Block> will work.

If it's really dynamic, you could make your trait have one method that returns enum EitherBlockOrInline { Block(&dyn Block), Inline(&dyn Inline) }, but you'll still need specific implementations of it.

2 Likes

I think of it as the default implementation that works for all concrete types except for those where it’s overridden.

I know, but if I have T: Downcast, how do I know which one won't panic? It seems like a trait that can't be used properly without some external knowledge.

1 Like

Ah, got it! Good point. It doesn’t matter in my case: I have a transforming iterator over an AST where I trust the callback to return the right kind of value (but can’t express that via a type, statically).

Given that your approach enables more use cases, it’s probably better.

For example, if you have something like struct Either<T: Downcast> { is_inline: bool, block_or_inline: T } then you could use Downcast without panicking, but you could replace both fields with an enum instead.

If you want to have both methods, then returning Option<Box<dyn Block>> is more idiomatic.

Or the callback could take T: Block or just &dyn Block directly if it knows it wants to call to_block?

Yes your enum is a great foundation for any kind of method that should work for all Inlines or all Blocks.

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.