4

I have two traits, one ordinal (Foo), another generic (TypedFoo<T>). I have several structures, each of them have both traits implemented.

Is it possible to convert from Foo to TypedFoo<T>, without converting to an intermediate structure?

trait Foo {
    fn f(&mut self);
}

trait TypedFoo<T> {
    fn get(&self) -> T;
}

#[derive(Clone)]
struct Data(i32);

impl Foo for Data {
    fn f(&mut self) {
        self.0 += 1;
    }
}

impl TypedFoo<Data> for Data {
    fn get(&self) -> Data {
        self.clone()
    }
}

//struct Data2(f64);
//impl Foo for Data2...
//impl TypedFoo<Data2> for Data2..

fn main() {
    let v: Vec<Box<Foo>> = vec![Box::new(Data(1))];    
} 

I can change Foo to this:

trait Foo {
    fn f(&mut self);
    fn get_self(&self) -> &Any;
}

Then get v[0].get_self() downcast_ref to Data, and then Data to &TypedFoo<Data>.

But is it possible to get &TypedFoo<Data> from &Foo without knowing "data type", some analog of Any but for a trait.

I imagine syntax like this:

let foo: &Foo = ...;
if let Some(typed_foo) = foo.cast::<Data>() {

} 

My question is different from Can I cast between two traits? because I have one generic and one ordinal trait. If I had two ordinal traits then the solution would be as simple as:

trait Foo {
    fn f(&mut self);
    fn as_typed_foo(&self) -> &TypedFoo;
}

Since TypedFoo is generic, none of the answers in that question help me. One possible solution could be:

trait Foo {
    fn f(&mut self);
    fn cast(&mut self, type_id: ::std::any::TypeId) -> Option<*mut ::std::os::raw::c_void>;
}

I am not sure how safe it is to cast *mut TypedFoo<T> -> *mut ::std::os::raw::c_void and then back to *mut TypedFoo<T>.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user1244932
  • 7,352
  • 5
  • 46
  • 103
  • Related information can be found in [How to get a struct reference from a boxed trait?](https://stackoverflow.com/q/33687447/155423); [Why doesn't Rust support trait object upcasting?](https://stackoverflow.com/q/28632968/155423) – Shepmaster Jun 05 '17 at 12:37
  • @Shepmaster I update my question to explains how it different from question you mentioned. – user1244932 Jun 05 '17 at 14:15

1 Answers1

3

The signature of the function that you want is

fn convert<T>(x: &Box<Foo>) -> &TypedFoo<T>

To type check this signature compiler must know that the type inside Box implements TypedFoo<T> for some T. But conversion into trait-object erases information about the real type. Which means that it is impossible to statically type check signature of this function.

So we need to do it dynamically and if we want to use types a current crate doesn't know about, we'll need to resort to unsafe.

One option is to limit a set of types, which can be used in TypedFoo, and provide conversion functions in the Foo trait. This allows to avoid unsafe.

Playground link

Second option is to add to trait Foo a function, which returns a slice of pairs (TypeId, *const ()). The pointer is a type erased pointer to function, which does actual conversion. Conversion function searches required type identifier and executes corresponding function.

Playground link

For the sake of demonstration I used Vec instead of slice in conversion_registrar. But it shouldn't be too hard to change return type to &'static [(TypeId, *const ())], using lazy_static crate.

red75prime
  • 3,733
  • 1
  • 16
  • 22
  • Thanks, looks like I made it not clear, but specific `Foo` for specific `Data` can only be converted to only `TypedFoo`, not for several `TypedFoo`, `TypedFoo`. Can compiler in theory compiler generate wrong code if function return pointer, but we call it as function that return reference? – user1244932 Jun 07 '17 at 21:44
  • @user1244932, If you want to return `TypedFoo` for `Box`, where `T` is the type behind trait `Foo` in this specific box, then it isn't possible, because type of return value cannot depend on the value of input parameter. Rust isn't dynamically typed language. The closest you can get is by defining enumeration of `&TypedFoo` values with different types. If you need to make a clone of `Box`, you can add function `fn clone_box(&self) -> Box` to the trait `Foo`. – red75prime Jun 08 '17 at 03:17
  • @user1244932, I suspect that converting function pointer into function pointer with different signature and calling it is undefined behavior. Note, that in the example I given, I convert `*const ()` into the function pointer of the same signature it was originally. – red75prime Jun 08 '17 at 03:27