Type parameter not used on enums

You could also make R an associated type of Page instead of a generic parameter, if appropriate in your case.

Yes, I also considered that, but I want to have distinct Pages for different Rs. If it was an associated type I could only have one Page implementation for each MyPage.

At this point it is not only about my specific case anymore, but about the general problem of parameterizing trait bounds in type definitions. I think there may always be legitimate reasons as to why you would want a construct like this:

enum MyEnum<R, P: Page<R>> {
    SetTitle(<P as Page<R>>::Title),
}

This is basically the need for higher order traits or Kinds, which still lacks support in the language.

2 Likes

That's not obvious to me on the MyEnum type itself.

I totally agree that we want GATs and such, but it's not at all clear to me that it's valuable to tie that enum to those types just to get other types off them -- notably, the compiler has been making some changes in exactly the opposite direction recently. Monomorphizing a copy of MyEnum (and, more relevantly, it's methods) for every permutation of P and R is not great for code size and compilation time, especially if it's common for all those pages to use the same type for their title (as seems highly likely).

For the ergonomics part one could consider allowing P::MyEnum to be an associated type giving MyEnum<P::Title>, or something like that.

The general rule of thumb is to use the smallest set of bounds necessary to correctly define your type, not use it.

In your initial case, as others stated, this would look like:

enum Msg<P: Component> {
    Page(<P a Component>::Msg),
    UrlChanged(subs::UrlChanged),
}

If you need to add your SetTitle variant, then it’s fine to change the bound:

enum Msg<R, P: Page<R>> {
    ...
    SetTitle(<P as Page<R>>::Title),
}

and you’ll note that this compiles fine and does not exhibit a « parameter is never used » error.

Then, on individual methods, you add all the bounds you need for your implementation.

There are multiple advantages to this rule of thumb:

  • you can fine tune bounds for specific use cases, for example Hash + Eq is needed in the general HashMap API, but not in the (nightly) raw_entry API
  • you don’t need to carry your bounds everywhere even when you don’t need them (although this may be relaxed with implied bounds, as @scottmcm noted)
  • you can add new behaviors with more constrained bounds without breaking code
  • you’re almost always sure not to hit the « parameter is never used » error
3 Likes

Oh that is right. Neat. Is that because R now is used in the enums memory layout definition?

So the only limitation is that I can not constrain Rust types based on type parameters that do not constrain the memory layout..? That certainly makes sense from a memory perspective. From an abstract perspective I wish I didn't have such a limitation and could just tell the compiler to put those constraints on that type in any case. You could have much more expressive type models. But I can also see how this abstraction could end up as confusing and it would make it a lot easier to increase code size and compilation time. So I guess the existing methods are already pretty good.