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 Page
s for different R
s. 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.
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 generalHashMap
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
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.