Store arbitrary types in a matrix and access them "by column"

In a Rust project I have I would like to be able to store arbitrary types in a matrix (similar to e.g. a python list of lists), and then sort the matrix by columns in different ways. I realise this is difficult to do in a completely generic way in Rust, so I'm trying to work around the problem.

One approach I have is to store the "rows" as a struct with fields according to what types I want, and then store them in a Vector. Then, when addressing the different "columns" of the data (e.g. the same field across each struct in the vec), I would like to use a "column-index" (0 for field 0, 1 for field 1 etc), to avoid having to reference them by their field-name. This works fine if the types of the structs fields are the same, but doesn't work if I use a generic type-parameter as return type. The ultimate goal with this approach is to write a proc_macro which adds this to an arbitrary "basic" struct (e.g. only containing "basic types"), so hard-coding it for each struct is something I would like to avoid. I have the proc_macro code producing the same get_column-implementation below so that is not an issue. The issue is to get the get_column to work.

Questions:
1/ Is there an other more preferable way of storing a generic matrix-like data-structure with columns of different types?
2/ If not, is there a work-around solution to create my get_column-function for a generic data type (see my MWE below)?
3/ Perhaps another approach is to wrap the row-elements in a Box or something similar? (I did not include the Box-idea code here, but did not get it to work in a generic way either...)

Below is my attempt as a MWE:

// This works, but is not generic enough
#[derive(Debug, Clone)]
struct A { x: i64, y: i64 }  // struct has several fields, but all with the same type
impl A {
    fn get_column(&self, column: usize) -> Result<i64, String> { // return type is obvious so it can be hardcoded...
        if column == 0 { return Ok(self.x.clone()) }
        else if column == 1 { return Ok(self.y.clone()) } 
        else { return Err("Invalid column number.".to_string()) };
    }
}
fn main() {
    let a: A = A {x: 3, y: 2};
    let val_a = a.get_column(0);
}

/* ********************************************** */

// This generic approach doesn't work
// Produces the error "expected type parameter `T`, found `u64`",
// which is odd to me since it then looks like the compiler actually knows the type
// and shouldnt complain at all...?
#[derive(Debug, Clone)]
struct B { x: i64, y: u64 }  // fields have different types
impl B {
    // get_column has generic type T.
    fn get_column<T: Clone>(&self, column: usize) -> Result<T, String> {
        if column == 0 { return Ok(self.x.clone()) }
        // etc for other fields...

You should take a look at the DataFrame struct from the polars crate: Polars — Rust data encoding library // Lib.rs

Thank you, that is an interesting option. I defently look into it.

However from a "I want to learn Rust"-perspective: is there any way of returning a generic type from my get_column-implementation? My research indicates that it might be difficult, but might be possible for a struct only containing "basic types".

It's not going to work exactly like in your example. Generics can be absolutely anything that fits its trait bounds, including types that haven't been invented yet. The caller decides what it should be. One could, for example, call it like this: my_b.get_column::<HashMap<String>>(0).

What you can do if you want to restrict it to types that can be converted from i64 is to have T: From<i64> and create it with T::from(self.x).

Yes. If you want to understand generics in Rust, you must understand that when you use them in a function, it's the caller who defines them not the callee. That's why you got the error expected type parameter T, found ....

That being said, you'll understand that 99.9% returning T is not what you want. Better abstractions in this case are:

  • an Enum with two variants that wrap i64 and u64
  • Find a trait that is common to both i64 and u64, and return a trait object

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.