How do traits with generics and lifecycles get called in rust

use std::collections::HashMap;

trait Model<'a, I, L, S, D> {
    fn info(&self, req: &'a mut I);
    fn list(&self, req: &'a mut L);
    fn save(&self, data: S);
    fn del(&self, req: D);
}

struct TidbModel;

impl<'a, I, L> Model<'a, QueryRequest<'a, I>, ListRequest<'a, L>, SaveData, QueryFilter>
    for TidbModel
{
    fn info(&self, req: &'a mut QueryRequest<'a, I>) {
        println!("----info-----");

        // req data needs to be modified
    }

    fn list(&self, req: &'a mut ListRequest<'a, L>) {
        println!("----list-----");

        // you need to modify the data data of the query in req
    }

    fn save(&self, data: SaveData) {
        println!("----save-----");
    }

    fn del(&self, req: QueryFilter) {
        println!("----del-----");
    }
}

struct QueryRequest<'a, T> {
    data: &'a mut T,
}
struct ListRequest<'a, T> {
    query: QueryRequest<'a, T>,
}
type SaveData = Vec<String>;
type QueryFilter = HashMap<String, String>;

// Here is an example call
fn main() {
    let mut tidb_model = TidbModel;

    // Call the info method
    let mut i = 10;
    let mut query_request = QueryRequest { data: &mut i };
    tidb_model.info(&mut query_request);

    // Call list method
    let mut l = "list_data";
    let mut list_request = ListRequest {
        query: QueryRequest { data: &mut l },
    };
    tidb_model.list(&mut list_request);

    // Call the save method
    let save_data = vec!["save_item1".to_string(), "save_item2".to_string()];
    tidb_model.save(save_data);

    // Call del method
    let query_filter = HashMap::from([("key1".to_string(), "value1".to_string())]);
    tidb_model.del(query_filter);
}

The error is as follows:

In addition to solving the above solution, what is the room for optimization in such a writing

Post errors as text.


It would be possible to call

<TidbModel as Model<'_, _, ListRequest<'_, L>, _, _>::info 

with any L, because it doesn't appear in the implementing type or in the arguments to the method, and L is unconstrained in the implementation, so there's nothing that could constrain the type in the call. But it's still possible for the body of info to depend on what L is, so the compiler wants to be able to decide on a specific type for L.

In general, if a type is ambiguous from the implementation header and the types at the call site, Rust is going to make you disambiguate.


Incidentally, this signature is never something you want.

fn info(&self, req: &'a mut QueryRequest<'a, I>) {
5 Likes

Feel this call, is not a little cumbersome, there is no simple way to write

What could possibly be any simpler than throwing your hands up and saying "compiler, just infer the lifetimes, I don't care"?

1 Like

I agree.

But I thought there might be a way to avoid the type annotations on every method call, so I tried attaching them to the TidbModel struct with a PhantomData. Here is a playground that works but still involves some repetition. Maybe there is a simpler way. My skills are lacking here.


Oh, the call to new doesn't need any type annotations:

    let tidb_model = TidbModel::new();

Fixed playground.


Last fix: We only need the I and L type params on the struct:

struct TidbModel<'a, I, L> {
    _phantom: PhantomData<&'a (I, L)>,
}

impl<'a, I, L> Model<'a, QueryRequest<'a, I>, ListRequest<'a, L>, SaveData, QueryFilter>
    for TidbModel<'a, I, L>

Playground

In this case, almost all the lifetime can be removed and will be inferred.
Playground with lifetimes removed and my earlier changes.

2 Likes