1

I have multiple levels of traits and can't work out how to reference the types in the top level object. I get messages about parameters not used, but if I remove them I get messages about them missing! An example:

struct User {
  name: String,
}

trait UserStore {
  fn get_user(&self) -> User;
}

struct Tenant<U>
where
  U: UserStore,
{
  user_store: U,
}

trait TenantStore<U>
where
  U: UserStore
{
  fn get_tenant(&self) -> Tenant<U>;
}

// what to do here??
struct Application<T, U>
where
    T: TenantStore<U>,
    U: UserStore
{
  tenant_store: T,
}

Error:

parameter `U` is never used

unused parameter

help: consider removing `U`, referring to it in a field, or using a marker such as `std::marker::PhantomData`rustc(E0392)

If I remove the U I get:

cannot find type `U` in this scope
  • 1
    I bet you want an associated type instead of a type parameter. See [Struct with a generic trait which is also a generic trait](https://stackoverflow.com/questions/42094284/struct-with-a-generic-trait-which-is-also-a-generic-trait) – trent Oct 05 '20 at 21:06
  • [That answer applied here](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=246b9e6634f0184ebf0bec5f44789e77). Although I would recommend stripping off the `where` clauses from the `struct`s; they do not appear to serve any purpose. – trent Oct 05 '20 at 21:07
  • @trentcl it looks like associated types might be what I'm after. But don't the `where` clauses on the structs have a purpose in constraining the type of the generic parameter? I don't want to have a `Tenant` for example. Or am I missing something? – Jeff Williams Oct 06 '20 at 11:41
  • Trait bounds on structs are usually only useful if the *struct definition itself* uses the trait in some way. For instance, [`std::iter::Peekable`](https://doc.rust-lang.org/src/core/iter/adapters/mod.rs.html#1425-1429) contains an `I::Item`, so removing the `I: Iterator` bound would obviously fail to compile. When the trait is not used in the struct itself, but only in `impl` blocks that define its behavior, it's less redundant to only add the bound to the `impl` blocks where it is needed... – trent Oct 06 '20 at 12:33
  • ... as in [`std::collections::HashMap`](https://doc.rust-lang.org/src/std/collections/hash/map.rs.html#203-205), which does not bound `K` with `Eq + Hash` or `S` with `BuildHasher` except in the `impl` blocks that define methods that use those traits. After all, if somebody hypothetically figures out a use for `Tenant`, is it really important that you stop them? Until [implied bounds](https://github.com/rust-lang/rfcs/pull/2089) are implemented, adding the bound on `Tenant` is (IMO) just a waste of typing. The bounds on the `impl` blocks suffice. – trent Oct 06 '20 at 12:44

1 Answers1

-1

Original answer

You can use PhantomData to solve the issue. Then the solution will look like (playground)

struct User {
  name: String,
}

trait UserStore {
  fn get_user(&self) -> User;
}

struct Tenant<U>
where
  U: UserStore,
{
  user_store: U,
}

trait TenantStore<U>
where
  U: UserStore
{
  fn get_tenant(&self) -> Tenant<U>;
}

// what to do here??
struct Application<T, U>
where
    T: TenantStore<U>,
    U: UserStore
{
  tenant_store: T,
  phantom_u: std::marker::PhantomData<U>
}

Update#1

It seems a bit ugly as I now have an extra parameter I need to fill in every time I create an Application, but which will never be used.

You can solve that by using Associated types. Then the solution will looks like (playground):

struct User {
  name: String,
}

trait UserStore {
  fn get_user(&self) -> User;
}

struct Tenant<U>
where
  U: UserStore,
{
  user_store: U,
}

trait TenantStore
{
  type U: UserStore;
  fn get_tenant(&self) -> Tenant<Self::U>;
}

struct Application<T>
where
    T: TenantStore
{
  tenant_store: T,
}
MaxV
  • 2,601
  • 3
  • 18
  • 25
  • It'll be helpful if there will be a comment with explanation why my post have `-1`? Does it incorrect? Do we already have the question/answer for the same problem which I missed during my search for duplicates? – MaxV Oct 05 '20 at 20:49
  • I saw that was one of the suggestions, but is that the only way to achieve this? It seems a bit ugly as I now have an extra parameter I need to fill in every time I create an Application, but which will never be used. – Jeff Williams Oct 05 '20 at 20:50
  • It's the only way I know. It may looks imperfect but it works. – MaxV Oct 05 '20 at 20:51
  • 1
    Could you justify why you used `PhantomData` and not, for instance, `PhantomData U>`, `PhantomData U>`, `PhantomData`, `PhantomData<*mut U>`, or `PhantomData<*const U>`? Because if you're just using `PhantomData` to silence the error message, chances are pretty good you picked the wrong one. – trent Oct 05 '20 at 20:57
  • Hi @trentcl, thanks for your question. I'm not sure I understand your question correct. I've use `PhantomData` to show the compiler that the final type of the monomorphized structure will depends on `U`. I'm not an expert in that area BTW. I believe it'll be very helpful if you can provide explanation: when each of the approaches can be used. It may be a brilliant answer for the question itself. – MaxV Oct 05 '20 at 21:09