Creating a table with dynamic columns and filters

My goal is to create a Table struct that looks like

Table {
    data: vec![
        Data {
            id: 1,
            name: "John".to_string(),
            currency: Currency::Usd,
        },
        Data {
            id: 2,
            name: "Mary".to_string(),
            currency: Currency::Aud,
        },
    ],
    columns: vec![id, name, currency],
}

So there are two rows of data and the 3 columns on each.
The columns could have a filter but it is optionally.

// I don't need a filter
let id = Column::new("Id", Field::Id);

// I need a filter that is String
let name = Column::new("Name", Field::Name).filter(Filter {
    list: vec!["John".to_string()],
    onfilter: |name: String, data: Data| -> bool { data.name == name },
});

// I need a filter that is an enum
let currency = Column::new("Currency", Field::Currency).filter(Filter {
    list: vec![Currency::Usd, Currency::Aud],
    onfilter: |currency: Currency, data: Data| -> bool { data.currency == currency },
});

Problem 1

However, due to the nature of the filters(have different types), I cannot get it to work. I considered using trait object(Vec<Box<dyn WHAT?>>) but I don't know what type to use.

error[E0308]: mismatched types
   --> src/main.rs:113:33
    |
91  |         onfilter: |name: String, data: Data| -> bool { data.name == name },
    |                   ---------------------------------- the expected closure
...
97  |         onfilter: |currency: Currency, data: Data| -> bool { data.currency == currency },
    |                   ---------------------------------------- the found closure
...
113 |         columns: vec![id, name, currency],
    |                                 ^^^^^^^^ expected `String`, found `Currency`
    |
    = note: expected struct `Column<&_, _, Html::Filter<Vec<String>, {closure@src/main.rs:91:19: 91:53}>>`
               found struct `Column<&_, _, Html::Filter<Vec<Currency>, {closure@src/main.rs:97:19: 97:59}>>`

Problem 2

Because my Filter struct looks like

pub struct Filter<L, F> {
    // A list of value to choose from, for example
    // let list = vec!["USD", "AUD"];
    list: L,
    // A closure to apply the filter,
    // FnMut() -> bool {}
    onfilter: F,
}

When I don't specify a filter(the filter is optional and not needed) to a column, I need to annotate it. Is there way to get way with it?

error[E0282]: type annotations needed for `Column<&str, Field, _>`
  --> src/main.rs:86:9
   |
86 |     let id = Column::new("Id", Field::Id);
   |         ^^   ---------------------------- type must be known at this point
   |
help: consider giving `id` an explicit type, where the type for type parameter `L` is specified
   |
86 |     let id: Column<&str, Field, L> = Column::new("Id", Field::Id);
   |           ++++++++++++++++++++++++

Probably Vec<Box<dyn FnMut(&Data) -> bool>> (or Fn). The Currency (or other column type) is not a param of the callback, because it is not passed/supplied by Table::filter. Rather it is captured by the callback when the callback is created.

My assumption is that the problem you're trying to solve is to write the filter function below from your playground. I don't see why you'd need to add the filters to a temporary Vec. You could just call the FnMut for each &Data row in your Table, and add the qualifying rows (for which all filters return true) to a Vec that the function returns.

    impl<T, K, L> Table<T, K, L> {
        pub fn filter(&self) -> Vec<Data> {
            let mut filters = Vec::new();
            for c in &self.columns {
                if let Some(ref filter) = c.filter {
                    filters.push(filter);
                }
            }

            // apply the filters to the `data` and
            // return a copy of it.
            todo!("not done yet")
        }
    }

Yes, exactly!

Take a look at the last two lines of the error message (redacted):

expected struct Column<_, _, Html::Filter<Vec<String>>>
   found struct Column<_, _, Html::Filter<Vec<Currency>>>

What’s happening here is:

let name: Column<_, _, Html::Filter<Vec<String>>> = Column::new(...);
let currency: Column<_, _, Html::Filter<Vec<Currency>>> = Column::new(...);

let columns = vec![name, currency];

Naturally, this won’t work because name and currency are of different types, but to store elements in a Vec, they all need to be the same type.

So, if you want to collect your Column elements into a vector (or similar container), the type parameters for Column must be the same for all elements—which, in this case, is not what you want. The type of the filter function can't appear as a type parameter of Column, but at the same time, you need to call the filter callback with different types.

This highlights a deeper design flaw in the current setup.

Just a heads-up: since Rust is statically typed, a function like this:

fn magic<T>(value: SomeType, unknown: T) {
    match value {
        some_value_1 => func_with_para_u64(unknown),
        some_value_2 => func_with_para_String(unknown),
        some_value_3 => func_with_para_Currency(unknown),
    }
}

...is simply not possible.

Of course, you could explore the powerful any module in the standard library (std::any - Rust), which allows you to do all sorts of dynamic type magic.

But my real question is: What exactly are you trying to achieve? What’s the main problem you're solving?

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.