11

Is there a way to cast from one trait to another?

I have the traits Foo and Bar and a Vec<Box<dyn Foo>>. I know some of the items in the Vec implement the Bar trait, but is there any way I could target them?

I don't understand if this is possible or not.

trait Foo {
    fn do_foo(&self);
}

trait Bar {
    fn do_bar(&self);
}

struct SomeFoo;

impl Foo for SomeFoo {
    fn do_foo(&self) {
        println!("doing foo");
    }
}

struct SomeFooBar;

impl Foo for SomeFooBar {
    fn do_foo(&self) {
        println!("doing foo");
    }
}

impl Bar for SomeFooBar {
    fn do_bar(&self) {
        println!("doing bar");
    }
}

fn main() {
    let foos: Vec<Box<dyn Foo>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];

    for foo in foos {
        foo.do_foo();

        // if let Some(val) = foo.downcast_whatever::<Bar>() {
        //     val.bar();
        // }
    }
}

[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b637bddc4fc923ce705e84ad1d783d4)
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Christoph
  • 26,519
  • 28
  • 95
  • 133
  • possible duplicate of http://stackoverflow.com/questions/25246443/rust-downcasting-and-boxany – Paolo Falabella Dec 22 '15 at 16:23
  • I think it's related but not exactly the same. In addition it's 1.5 years old so the chances are high that something changed about that ;) – Christoph Dec 22 '15 at 17:23
  • The closest thing I found was this http://stackoverflow.com/questions/27073799/rust-vector-of-traits-cast-each-trait?rq=1 But it ends with a compiler bug (unfortunately without a link) and is kind of old, too. – Christoph Dec 22 '15 at 18:01
  • 1
    @Christoph Shepmaster has given you several options (and Paolo gave you the jackhammer - use wisely), but without some context about what these traits *mean* it's hard to give concrete answers about how to model the solution. The first, and most often best, instinct is an enum, but whether (and how) that's a fit for your problem is really up in the air. – Veedrac Dec 23 '15 at 01:11

5 Answers5

12

No. There is no way to cast between two unrelated traits. To understand why, we have to understand how trait objects are implemented. To start with, let's look at TraitObject.

TraitObject is a reflection of how trait objects are actually implemented. They are composed of two pointers: data and vtable. The data value is just a reference to the original object:

#![feature(raw)]

use std::{mem, raw};

trait Foo {}
impl Foo for u8 {}

fn main() {
    let i = 42u8;
    let t = &i as &dyn Foo;
    let to: raw::TraitObject = unsafe { mem::transmute(t) };

    println!("{:p}", to.data);
    println!("{:p}", &i);
}

vtable points to a table of function pointers. This table contains references to each implemented trait method, ordered by some compiler-internal manner.

For this hypothetical input

trait Foo {
    fn one(&self);
}

impl Foo for u8 {
    fn one(&self) { println!("u8!") }
}

The table is something like this pseudocode

const FOO_U8_VTABLE: _ = [impl_of_foo_u8_one];

A trait object knows a pointer to the data and a pointer to a list of methods that make up that trait. From this information, there is no way to get any other piece of data.

Well, almost no way. As you might guess, you can add a method to the vtable that returns a different trait object. In computer science, all problems can be solved by adding another layer of indirection (except too many layers of indirection).

See also:

But couldn't the data part of the TraitObject be transmuted to the struct

Not safely, no. A trait object contains no information about the original type. All it has is a raw pointer containing an address in memory. You could unsafely transmute it to a &Foo or a &u8 or a &(), but neither the compiler nor the runtime data have any idea what concrete type it originally was.

The Any trait actually does this by also tracking the type ID of the original struct. If you ask for a reference to the correct type, the trait will transmute the data pointer for you.

Is there a pattern other than the one I described with my FooOrBar trait to handle such cases where we need to iterate over a bunch of trait objects but treat some of them slightly different?

  • If you own these traits, then you can add as_foo to the Bar trait and vice versa.

  • You could create an enum that holds either a Box<dyn Foo> or a Box<dyn Bar> and then pattern match.

  • You could move the body of bar into the body of foo for that implementation.

  • You could implement a third trait Quux where calling <FooStruct as Quux>::quux calls Foo::foo and calling <BarStruct as Quux>::quux calls Bar::foo followed by Bar::bar.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • But couldn't the `data` part of the `TraitObject` be transmuted to the `struct` and once we have the struct we can safely cast into the other trait? Well, but even if that worked it won't for stable rust. So, is there a pattern other than the one I described with my `FooOrBar` trait to handle such cases where we need to iterate over a bunch of trait objects but treat some of them slightly different? – Christoph Dec 22 '15 at 19:07
  • 2
    I would not that C++ also uses a v-table and stores run-time type information there on top of the regular list of virtual methods, so the use of a v-table in itself does not preclude down-casting. It is just that Rust does not store such information. – Matthieu M. Dec 23 '15 at 10:08
3

Here is what I did.

I added an as_bar method to the Foo trait that returns an Option<&Bar>. I gave the trait a default implementation to return None so that there is little to no inconvenience for Foo implementers that don't bother about Bar.

trait Foo {
    fn do_foo(&self);

    fn as_bar(&self) -> Option<&dyn Bar> {
        None
    }
}

I overwrite that method for the SomeFooBar struct which implements both Foo and Bar to return Some(self):

impl Foo for SomeFooBar {
    fn do_foo(&self) {
        println!("doing foo");
    }

    fn as_bar(&self) -> Option<&dyn Bar> {
        Some(self)
    }
}

Which makes the calling code look pretty much the way I want it to look.

fn main() {
    let foos: Vec<Box<dyn Foo>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];

    for foo in foos {
        foo.do_foo();

        if let Some(bar) = foo.as_bar() {
            bar.do_bar();
        }
    }
}

Playground

I would love to see Rust improve on this part in the future, but it's a solution I can totally live with for my case.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Christoph
  • 26,519
  • 28
  • 95
  • 133
2

so... I don't think this is exactly what you want, but it's the closest I can get.

// first indirection: trait objects
let sf: Box<Foo> = Box::new(SomeFoo);
let sb: Box<Bar> = Box::new(SomeFooBar);

// second level of indirection: Box<Any> (Any in this case
// is the first Box with the trait object, so we have a Box<Box<Foo>>
let foos: Vec<Box<Any>> = vec![Box::new(sf), Box::new(sb)];

// downcasting to the trait objects
for foo in foos {
    match foo.downcast::<Box<Foo>>() {
        Ok(f) => f.do_foo(),
        Err(other) => {
            if let Ok(bar) = other.downcast::<Box<Bar>>() {
                    bar.do_bar();
            }
        }
    }
}

note that we can call SomeFooBar as a Box<Bar> only because we stored it as a Box<Bar> in the first place. So this is still not what you want (SomeFooBar is a Foo too, but you can't convert it to a Box<Foo> any longer, so we're not really converting one trait to the other)

Paolo Falabella
  • 24,914
  • 3
  • 72
  • 86
  • Interesting approach but as you noted it's not exactly what I need because I have to decide for `Foo` or `Bar` but can't handle a type as being both `Foo` and `Bar` at the same time. – Christoph Dec 23 '15 at 14:11
2

The short answer is: there is extremely limited support for downcasting at the moment in the language.


The long answer is that being able to downcast is not seen as high-priority for both technical and philosophical reasons:

  • from a technical stand-point, there are workarounds for most if not all situations
  • from a philosophical stand-point, downcasting leads to more brittle software (as you unexpectedly start relying on implementation details)

There have been multiple proposals, and I myself participated, but for now none has been selected and it is unclear whether Rust will ever get downcasting or if it does what its limitations will be.

In the mean time, you have essentially two workarounds:

  1. Use TypeId: each type has an associated TypeId value which can be queried, then you can build a type-erased container such as Any and query whether the type it holds is a specific X. Behind the scenes Any will simply check the TypeId of this X against the TypeId of the value stored.

  2. Create a specific trait, as you did.

The latter is more open-ended, and notably can be used with traits, whereas the former is limited to concrete types.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • That's interesting. Can you elaborate on the `TypeId` part a bit? Maybe with a bit of code to demonstrate it, I think I haven't fully groked how that workaround is supposed to work. – Christoph Dec 23 '15 at 14:20
  • @Christoph: I linked to `std::any::Any` which uses `TypeId` in the background, or you can check the github repository I linked to to see how to create your own downcasting facilities based on `TypeId`... but really I just mentioned either for completeness. `Any` does not allow downcasting, it just allows retrieving the original type, and `rust-poly` is a proof of concept. The only current generic work-around is to build downcasting in a trait, that's what Servo does. – Matthieu M. Dec 23 '15 at 14:29
  • Ok, thank you. I hope to see Rust improve on that front in the future. I updated my question with the solution that I went for in the meantime. – Christoph Dec 23 '15 at 14:57
  • @Christoph: Ah! Please do not mix question and answer in the question! Instead, you can perfectly post an answer to your own question with the solution; I think the solution you chose is indeed quite idiomatic. For the record though, I am not myself a fan of downcasting (because it introduces brittleness) so I do not miss it and would quite favor never having it. – Matthieu M. Dec 23 '15 at 15:04
  • Sorry, corrected it ;) I actually don't need downcasting that often either. I'll see how I manage without it ;) – Christoph Dec 23 '15 at 15:19
0

The only solution that I found originally is to introduce a third trait FooOrBar with explicit converter methods and implement that for both types. It doesn't feel like the right tool for the job though.

trait FooOrBar {
    fn to_bar(&self) -> Option<&dyn Bar>;
    fn to_foo(&self) -> Option<&dyn Foo>;
}

impl FooOrBar for SomeFooBar {
    fn to_bar(&self) -> Option<&dyn Bar> {
        Some(self)
    }

    fn to_foo(&self) -> Option<&dyn Foo> {
        None
    }
}

impl FooOrBar for SomeFoo {
    fn to_bar(&self) -> Option<&dyn Bar> {
        None
    }

    fn to_foo(&self) -> Option<&dyn Foo> {
        Some(self)
    }
}

fn main() {
    let foos: Vec<Box<dyn FooOrBar>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];

    for foo in foos {
        foo.to_foo().map(|foo| foo.do_foo());
        foo.to_bar().map(|foo| foo.do_bar());
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366