I swear I searched all the internet and I tried hard to understand all answers that I found that seemed related. However, I still fail to 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<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();
}*/
}
}
Playpen: http://is.gd/CPnSCu
My question is basically if there is a way to cast from one trait to another.
I have two traits Foo
and Bar
and a Vec<Box<Foo>>
. I know some of the items in the vec implement the Bar
trait and I wonder if there's any way I could target them?
Update
The only solution that I found so far is to introduce a 3rd 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<&Bar>;
fn to_foo(&self) -> Option<&Foo>;
}
impl FooOrBar for SomeFooBar {
fn to_bar(&self) -> Option<&Bar> {
Some(self)
}
fn to_foo(&self) -> Option<&Foo> {
None
}
}
impl FooOrBar for SomeFoo {
fn to_bar(&self) -> Option<&Bar> {
None
}
fn to_foo(&self) -> Option<&Foo> {
Some(self)
}
}
fn main () {
let foos:Vec<Box<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());
}
}
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)]
trait Foo {}
impl Foo for u8 {}
use std::{raw, mem};
fn main() {
let i = 42u8;
let t = &i as &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];
So 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).
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<Foo>
or a Box<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
.
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)
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:
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.
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.
After reading through all those great answers 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
implementor that don't bother about Bar
.
trait Foo {
fn do_foo (&self);
fn as_bar(&self) -> Option<&Bar> {
None
}
}
Only for the SomeFooBar
struct that implements both Foo
and Bar
I need to overwrite that method to return Some(self)
.
impl Foo for SomeFooBar {
fn do_foo(&self) {
println!("doing foo");
}
fn as_bar(&self) -> Option<&Bar>{
Some(self)
}
}
Which makes the calling code look pretty much the way I want it to look.
let foos:Vec<Box<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();
}
}
Playpen: http://is.gd/JQdyip
I would love to see rust improve on that part in the future but it's a solution I can totally live with for my case.