Stumped on error for associated type in generic trait impl


#1

As a learning/play/hobby project, I’m making a small application where I attempt to decouple the data access from the rest of the application as much as possible. This way, I should later be able to experiment with other data managing frameworks or methods (database, on file, in memory; ORM vs fixed queries etc etc) with little impact to other (equally experimental) areas of this project.

In short, I’m trying to keep everything as generic as possible.

EDIT: The below examples aren’t very clear. See the third post for playground examples.

Right now, I have a generic DataStruct, looking somewhat like this (cut a bit to reduce noise in this question):

pub trait DataStruct
    where Self: Sized {

    type DataProvider;

    /// Gets all persisted structs from a data store.
    fn get_all(data_provider: &Self::DataProvider) -> Vec<Self>;
}

The idea behind the DataProvider associated type and passing it to the functions, is to avoid global state. I can make a DataProvider somewhere in the application, and pass it around.

In another module, for a specific data implementation, I have e.g. for the fictitious BazDB:

pub trait BazDbDataStruct: DataStruct {
    fn table_name() -> String;
    fn query_all(data_provider: &BazDbDataProvider) -> Vec<Self> {
        let conn = data_provider.open_baz_db_connection();
        conn.find_all_the_things_for_table(Self::table_name())
    }
}

Now, let’s see how this works for a persistable struct Foo:

struct Foo {
    id: i64,
    value: String
}

impl BazDbDataStruct for Foo {
    fn table_name() -> String {
        "FOO".to_string()
    }
}

impl DataStruct for Foo {
    type DataProvider = BazDbDataProvider;

    fn get_all(data_provider: &BazDbDataProvider) -> Vec<Self> {
        Self::query_all(data_provider)
    }
}

An important part of this question is the signature of this get_all implementation. I have tested this in a more complete example, and Rust seems to accept this without problems.

The problem comes when I now have a second struct corresponding to a table in my BazDB. The implementation for Bar would look as follows:

struct Bar{
    id: i64,
    other_value: String
}

impl BazDbDataStruct for Bar{
    fn table_name() -> String {
        "BAR".to_string()
    }
}

impl DataStruct for Bar{
    type DataProvider = BazDbDataProvider;

    fn get_all(data_provider: &BazDbDataProvider) -> Vec<Self> {
        Self::query_all(data_provider)
    }
}

So while the implementation for the concrete BazDbDataStruct is (obviously) different, the implementation for the generic DataStruct is (equally obviously) identical to that of Foo. Still, this all runs and tests fine.

However, I now try to genericize the DataStruct implementation to avoid duplication and boilerplate:

impl<T> DataStruct for T
        where T: BazDbDataStruct {

    type DataProvider = BazDbDataProvider;

    fn get_all(data_provider: &BazDbDataProvider) -> Vec<Self> {
        T::query_all(data_provider)
    }
}

This implementation is identical to the above two implementations, but Rust suddenly doesn’t like the method signature for get_all anymore:

error[E0053]: method `get_all` has an incompatible type for trait
   --> src\data\core\bazdb.rs:763:35
    |
763 |         fn get_all(data_provider: &BazDbDataProvider) -> Vec<Self> {
    |                                   ^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `data::core::bazdb::BazDbDataProvider`
    |
   ::: src\data\core\mod.rs:46:31
    |
46  |     fn get_all(data_provider: &Self::DataProvider) -> Vec<Self>;
    |                               ------------------- type in trait
    |
    = note: expected type `fn(&<T as data::core::DataStruct>::DataProvider) -> std::vec::Vec<T>`
               found type `fn(&data::core::bazdb::BazDbDataProvider) -> std::vec::Vec<T>`

So I sort of understand this error, but I also kinda don’t :slight_smile: Why is this a problem with this generic implementation, yet Rust had no problems when the two identical previous implementations (which had the same function signature). I wouldn’t be suprised if generics threw some spanners in the wheels somewhere, but I don’t know why, nor what to do about it. The DataProvider type does not depend on the generic T, it is defined in this impl. So why does Rust insist on needing &<T as data::core::DataStruct>::DataProvider here, yet was perfectly fine with the &BazDbDataProvider


#2

You need a couple of changes to stay generic.

trait BazDbDataStruct: DataStruct {
    ...
    // Make this take `&Self::DataProvider`
    fn query_all(data_provider: &Self::DataProvider) -> Vec<Self> { ...  }
}
// Then change the blanket impl to also use the associated types:
impl<T> DataStruct for T
        where T: BazDbDataStruct {

    type DataProvider = T::DataProvider; // the impl's associated type is whatever `T` expects

    fn get_all(data_provider: &Self::DataProvider) -> Vec<Self> {
        T::query_all(data_provider)
    }
}

Also, try to use the playground to create runnable/compilable examples - it’ll help people to help you.


#3

I don’t think that works.

I have made playground examples for this problem. I’ve made some changes to naming (BazDb wasn’t really very readable, I feel) and had to simplify implementations, but the relevant bits work identically.

This first example is the situation where there are two identical implementations for DataStruct. Note the function signatures where either &Self::DataProvider or &SpecificDataProvider (the former BazDbDataProvider) are expected. This compiles and runs.

This second example is the generic version described above. It yields an error when compiling.

Your solution (can’t link to it, since new users apparently can only post 2 links, but it’s gist=c98517470b66c609fa4e7e651e2e8656) also leads to two errors, which seem logical to me (error based on playground examples):

error[E0599]: no method named `get_property` found for type `&<Self as DataStruct>::DataProvider` in the current scope
  --> src/main.rs:20:35
   |
20 |         let value = data_provider.get_property(Self::property_index());
   |                                   ^^^^^^^^^^^^

error[E0275]: overflow evaluating the requirement `<Foo as DataStruct>::DataProvider`
  --> src/main.rs:95:15
   |
95 |     let foo = Foo::get_one(&data_provider);
   |               ^^^^^^^^^^^^

Firstly, DataStructImpl has lost the information that the data provider is a SpecificDataProvider (and thus has a get_property method).

Secondly, type DataProvider = T::DataProvider is a cyclical definition.
DataStructImpl: DataStruct and where T: DataStructImpl means that type DataProvider = T::DataProvider is the same as type DataProvider = Self::DataProvider. Which obviously can’t work (I think :slight_smile:)

I still don’t really understand why in the generic impl<T> DataStruct for T where T: DataStructImpl can’t seem to handle the associated type well…


#4

Based on your comments, I think you want this additional constraint on DataStructImpl:

trait DataStructImpl: DataStruct<DataProvider=SpecificDataProvider>

This will ensure that all impls use SpecificDataProvider, and then the playground works.


#5

Thanks! That works. And makes sense :slight_smile: I did not know about that syntax.