Search code examples
genericsrustdynamic-dispatch

Hiding and receiving generic T objects


The following Rust code is given:

struct Wrapper<T> {
    data: Vec<T>, // more attributes...
}

trait DataWrapper<T> {
    fn get_column(&self) -> &Vec<T>;
    fn get_data(&self, row: usize) -> &T;
}

impl<T> DataWrapper<T> for Wrapper<T> {
    fn get_column(&self) -> &Vec<T> {
        &self.data
    }

    fn get_data(&self, row: usize) -> &T {
        &self.data[row]
    }
}

struct Inter<T> {
    inter_value: Wrapper<T>,
}

trait GetInter {
    fn get_count(&self) -> &str;
}

impl GetInter for Inter<i32> {
    fn get_count(&self) -> &str {
        "i32"
    }
}

impl GetInter for Inter<f64> {
    fn get_count(&self) -> &str {
        "f64"
    }
}

fn create_vector() -> Vec<Box<GetInter>> {
    // Add some sample data
    let x = Wrapper { data: vec![1, 2, 3] };
    let y = Wrapper { data: vec![1.1, 2.2, 3.3] };

    let mut vec: Vec<Box<GetInter>> = Vec::new();

    let m = Inter { inter_value: x };
    let m = Box::new(m);
    let m = m as Box<GetInter>;
    vec.push(m);

    let n = Inter { inter_value: y };
    let n = Box::new(n);
    let n = n as Box<GetInter>;
    vec.push(n);

    vec
}

struct ColumnWrapper {
    columns: Vec<Box<GetInter>>, // more attributes
}

fn create_column_wrapper() -> ColumnWrapper {
    let result = create_vector();

    ColumnWrapper { columns: result }
}

fn main() {
    let result = create_column_wrapper();

    for iter1 in result.columns {
        println!("1: {}", iter1.get_count());
    }
}

Wrapper<T> stores a generic Vec, in detail a data column from an column store. The corresponding implementation returns the Vec or a specific element as reference.

The idea of the Inter<T> and GetInter traits is to hide the generic T object, which comes from the Wrapper's Vec<T> generic data type. The get_count() method is only for test purposes.

create_vector() creates two new Vecs with some sample data. The result is cast to GetInter, wrapped into a Box and stored in a Vec<Box<GetInter>>. Finally the caller will create a new ColumnWrapper element. Now a generic data representation is given.

After compiling and running the correct result is given:

i32 
f64

Now the real problem starts. I try to access the original data, which are stored in Wrapper<T>.

My first idea was to use the dynamic dispatching feature from Rust. It should detect the real data type during runtime.

The main() function is modified:

fn main() { 
    let result = create_column_wrapper();

    for iter1 in result.columns {
        println!("1: {}", iter1.get_count());

        for iter2 in dyn_dispatch(*iter1) {
            println!("2: {}", iter2);
        }
    }
}

The corresponding and not tested dyn_dispatch() function:

trait Foo<T> {
    fn method(x: &Inter<T>) -> &Vec<T>;
}

impl<i32> Foo<i32> for Inter<i32> {
    fn method(x: &Inter<i32>) -> &Vec<i32> {
        &x.inter_value.data
    }
}

impl<f64> Foo<f64> for Inter<f64> {
    fn method(x: &Inter<f64>) -> &Vec<f64> {
        &x.inter_value.data
    }
}

fn dyn_dispatch<T>(x: &GetInter) -> &Vec<T> {
    Foo::method(&x as &Inter<T>)
}

The compilation fails and an error is thrown:

85:2 error: conflicting implementations of trait Foo<_> for type Inter<_>: [E0119]

Any idea how to fix the compilation error or another and more easy idea to hide and access the generic T object?


Solution

  • There are a couple of problems here.

    The first error:

    error: conflicting implementations of trait Foo<> for type Inter<>: [E0119]

    actually refers to these:

    impl<i32> Foo<i32> for Inter<i32> { ... }
    impl<f64> Foo<f64> for Inter<f64> { ... }
    

    Since i32 and f64 are parameters, those are both equivalent to:

    impl<T> Foo<T> for Inter<T> { ... }
    

    That is, the implementation of Foo<T> for Inter<T> for any T hence the conflicting implementations. The fix is to write them as:

    impl Foo<i32> for Inter<i32> { ... }
    

    The next problem is that you're not actually doing dynamic dispatch. Your dyn_dispatch function would have to have T specified or inferred at compile time; it can't return a different type each time; similarly you can't just downcast from GetInter to Inter<T> like that. You need to do it in the same way as you've done GetInter::get_count.