But the _Unreachable variant should never be used, and there is no easy way to enforce this, and all of this still requires us to use this variant in any exhaustive patterns.
It would be nice if the compiler could recognize that the type parameter is used by a trait bound. It would also be useful to be able to disallow enum variants from being used at all.
You could put a ! (or std::convert::Infallible) in it, which would keep it from ever being constructed. (That of course doesn't solve the latter problem.)
Can you elaborate why you need the bound on the type? Can it just go on the methods instead?
It should store the current page. Pages also implement Component. Components communicate via messages which are propagated through the component tree.
This is why the Router message type Msg has to be able to store messages to its page children:
pub enum Msg<R: Route, P: Page<R>> {
Page(<P as Component>::Msg), // <- message to child page
UrlChanged(subs::UrlChanged),
GoTo(R), // <- this actually solved the issue for me now :D
}
so the Msg type must be generic, as far as I can tell.
(The issue is solved for me now, because I found a variant I need that actually uses R. But the general problem remains.)
No, that would actually work in this case. But there is a reason why I use the Page<R> trait and that is to define a set of types that can be used here, and they might get tighter requirements as my code develops. In this case simply using Component would work, but it would not really make sense because Router<R, P> requires P: Page<R> already. So this would really only be a workaround.
Also a good way of mitigating this.
In general though, I think Rust should recognize that R is being used in another trait bound. What is the reason why it is not doing that right now?
It is a common pattern to defer strong bounds to implementations. So in this case, you could demand Msg<P: Component> on definition, and only add impl<R: Route, P: Page<R>> in the impls where you need this.
This also makes sense: if you're not using R in the definition, why are you declaring it there? Note that adding PhantomData might change your code's behaviour (re: Drop), and rightly so. That's it's purpose, not to quiet the "unused types" warning. That said, if you do want to use it that way, it's probably better to use some weaker type, like PhantomData<&R>, or maybe even PhantomData<fn(R)->R>.
But the "unused type parameter" is an error, not a warning. That is why it is a problem, and that is why I am advocating to remove that error.
There doesn't seem to be an RFC for this, but I think it would really help users of the higher level type system. I also don't understand the reason why this should be an error. @alexcrichton said it was implemented to ensure that the compiler knows which constraints it can put on the type parameter. But I don't understand why these can not be derived by a trait bound for another type parameter.
I think the main issue is that R is not well defined. If you have more than one R: Router where also P: Page<R>, which one is the one relevant for this instance? This question can't be answered, because R is not actually relevant to the enum. It is, however, relevant for the method, so that's where it needs to be decided, with which R are you trying to run the method.
I mean, it works now, with some workarounds. But it isn't really pretty and carries a maintenance cost, also for structs, because you have to include the PhantomData field in all constructions and some patterns.
It would be great if this burden could be carried by the compiler. If there are multiple parameters i.e. <R1: Route, R2: Route, P1: Page<R1>, P2: Page<R2>> the compiler can just treat it like there is a PhantomData for each parameter, if that is necessary. There are not really any decisions to be made here, as far as I can tell. All that is needed would be a design proposal and someone to make the changes to the compiler. I would love to do this, but I have never worked on the rust compiler. It does seem like a good starting point though.. Maybe I will take some time for it.
The correct way to communicate to the compiler that you just want P to inform the storage of Msg<R, P> is to make the type just Msg<P>.
You'll find that most types in Rust under-bound their storage definition and only add most relevant bounds for impl blocks. (Whether that's desirable is a different question; I flip-flop often, and it's highly dependent on the individual type.)
But defining Msg as enum Msg<P: Component> would also require that other types in Msg may only expect Component from P, nothing more. For example, maybe Page has an associated type Title and I want to have a Msg::SetTitle(<P as Page<R>>::Title) to set the title of a page. I would have to define enum Msg<R: Route, P: Page<R>> to access that. That is just one example where relaxing the trait bound would not work.
Is the current instance of the enum a MyEnum<SomeRouter, MyPage> or a MyEnum<OtherRouter, MyPage>? If R isn't used anywhere, it doesn't make sense to distinguish between those two types. The instance is just MyEnum<MyPage>, and then it can be called with the method .route<SomeRouter> or .route<OtherRouter>.
trait Page<R> {
type Title: Into<R>;
}
struct MyPage;
struct A;
struct B;
impl Page<A> for MyPage {
type Title = A;
}
impl Page<B> for MyPage {
type Title = B;
}
enum Msg<R, P: Page<R>> {
SetTitle(<P as Page<R>>::Title),
}
Here Page's associated type is used in the enum, and is dependent on its type parameter R. So R is in fact constraining the type of Msg, in that SetTitle has to contain a Page<R>::Title which has to be Into<R>.
So Msg<A, MyPage> and Msg<B, MyPage> are not generally equivalent.
Given the current state of the language (and the lack of implied bounds), I think it's currently clearly good. Otherwise one needs to repeat so many bounds all over the place.
For example, it's really nice that I can debug-print a HashSet<T> with just T: Debug now -- when I used to have to say T: Eq + Hash too it was just pointless noise that kept #[derive] from working.
(There are language changes that could affect things here -- like the previously-mentioned implied bounds -- but I've been skeptical of the proposals thus far because of the semver implications. Maybe one day someone will come up with an awesome fix, though.)